home *** CD-ROM | disk | FTP | other *** search
/ Windows News 2010 Summer - Disc 1 / WN_Ete2010_CD1.iso / Onglet5 / Weezo / Weezo setup.exe / {code_appDir} / www / includes / musicDBFunctions.php < prev    next >
PHP Script  |  2010-05-19  |  80KB  |  1,952 lines

  1. <?php
  2. /**
  3.  * Database-based music explorer functions
  4.  * used by /res/explorer/music2 and /res/explorer/music3
  5.  *
  6.  * Display an audio files explorer based on
  7.  *     - iTunes music database or
  8.  *     - winamp music database or
  9.  *     - folders music database
  10.  *
  11.  * Audio files list
  12.  * Playlist functions
  13.  * Audio player (streaming)
  14.  *
  15.  * PHP version 5
  16.  *
  17.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  18.  * that is available through the world-wide-web at the following URI:
  19.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  20.  * the PHP License and are unable to obtain it through the web, please
  21.  * send a note to license@php.net so we can mail you a copy immediately.
  22.  *
  23.  * @category   NA
  24.  * @package    NA
  25.  * @author     Nicolas Bruley / Peer 2 World <contact@weezo.net>
  26.  * @copyright  2005-2009 Nicolas Bruley / Peer 2 World
  27.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  28.  * @version    CVS: $Id:$
  29.  * @link       http://www.weezo.net
  30.  * @since      File available since Release 1.0.5
  31.  */
  32.  
  33. // Winamp database columns identifiers
  34. define('FIELD_UNDEFINED',255);
  35. define('FIELD_COLUMN',0);
  36. define('FIELD_INDEX',1);
  37. define('FIELD_REDIRECTOR',2);
  38. define('FIELD_STRING',3);
  39. define('FIELD_INTEGER',4);
  40. define('FIELD_BOOLEAN',5);
  41. define('FIELD_BINARY',6);
  42. define('FIELD_GUID',7);
  43. define('FIELD_FLOAT',9);
  44. define('FIELD_DATETIME',10);
  45. define('FIELD_LENGTH',11);
  46. define('FIELD_STRINGUTF8',12);
  47. $_ENV['supportedPlaylists']=array('m3u','pls');
  48.  
  49.  
  50. function mDBPlaylistItemFromTrack($track){
  51.     return array('completeFileName'=>'*resSpecific*/'.$track['id'].'.mp3', 'name'=>'*resSpecific*/'.$track['id'].'.mp3', 'label'=>cfUTF8Decode(playlistLabelFromTrackInfo($track),true,true,false), 'externalRef'=>$track['id']);
  52. }
  53.  
  54.  
  55. /**
  56.  * @desc Winamp music database: convert an "encoded" audio or cover filename into real filename
  57.  *         audio encoded format: *resourceBasePath* /1234.mp3 where 1234 is track id in database
  58.  *         cover encoded format: *resSpecific* /(embeded|folder)1234.mp3 where 1234 is track id in database
  59.  * @param string $encodedFilename: encoded audio / cover filename
  60.  * @param bool $resampledAudioFile:
  61.  * @return string: real complete filename or false if not found / not allowed
  62.  */
  63. function mDBWinampExtAccessFunction($encodedFilename,&$resampledAudioFile){
  64.     if(substr($encodedFilename,0,14)!="*resSpecific*/" && substr($encodedFilename,0,19)!="*resourceBasePath*/") return false;
  65.     $cfn=substr($encodedFilename,strpos($encodedFilename,"*/")+2);
  66.  
  67.     // Cover
  68.     // Embeded in file tags: function should return actual filename
  69.     if(substr($cfn,0,7)=='embeded') $cfn=substr($cfn,7);
  70.     // Folder image: function should find and return best suitable image located in audio file's folder
  71.     elseif(substr($cfn,0,6)=='folder') {$cfn=substr($cfn,6);$folderImage=1;}
  72.  
  73.     if(!is_numeric($cfn=cfFileWithoutExtension($cfn))) return false;
  74.  
  75.     // Search for track in database
  76.     if(!($db=@sqlite_open(cfAppResourceDir()."/music.db"))) return false;
  77.     $cfn=sqlite_single_query('SELECT Location FROM tracks WHERE id="'.$cfn.'"',$db);
  78.     sqlite_close($db);
  79.  
  80.     // If not found, exit
  81.     if(!$cfn) return false;
  82.     $cfn=cfUTF8Decode($cfn,true,false,false);
  83.  
  84.     // If cover is requested and il must be searched for in file's directory
  85.     if(isset($folderImage))    {
  86.         require_once(INCLUDE_DIR.'explorerFunctions.php');
  87.         $coverCFN=efGetCoverFromFolder($cfn,$score);
  88.         // If no image found, return transparent (lightweight) image
  89.         return ($score>=0)?$coverCFN:cfAppDocRoot().'/gfx/v.gif';
  90.     }
  91.     if(cfFileExtension($cfn)!="mp3") $resampledAudioFile=true;
  92.     return $cfn;
  93. }
  94.  
  95. /**
  96.  * @desc Folder music database: convert an "encoded" audio or cover filename into real filename
  97.  *         audio encoded format: *resourceBasePath* /1234.mp3 where 1234 is track id in database
  98.  *         cover encoded format: actual filename
  99.  * @param string $encodedFilename: encoded audio / cover filename
  100.  * @param bool $resampledAudioFile:
  101.  * @return string: real complete filename or false if not found / not allowed
  102.  */
  103. function mDBFolderExtAccessFunction($encodedFilename,&$resampledAudioFile){
  104.     global $resampledAudioFile;
  105.  
  106.     // If called by /ext/image.php, verify that format is:
  107.     // (resourceBasePath)/(folder|embeded)XXXX.mp3
  108.     if(basename($_SERVER["SCRIPT_NAME"])=='image.php'){
  109.         $encodedFilename=str_replace('\\','/',$encodedFilename);
  110.         $encodedFilename=cfFileWithoutExtension(substr($encodedFilename,strrpos($encodedFilename,'/')+1));
  111.         if(substr($encodedFilename,0,6)=='folder') {$folderImage=1;$encodedFilename=substr($encodedFilename,6);}
  112.         elseif(substr($encodedFilename,0,7)=='embeded') $encodedFilename=substr($encodedFilename,7);
  113.         else return false;
  114.         if(!is_numeric($encodedFilename)) return false;
  115.  
  116.         // Search for track in database
  117.         if(!($db=@sqlite_open(cfAppResourceDir()."/music.db"))) return false;
  118.         $cfn=sqlite_single_query('SELECT Location FROM tracks WHERE id="'.$encodedFilename.'"',$db);
  119.         sqlite_close($db);
  120.  
  121.         // If not found, exit
  122.         if(!$cfn) return false;
  123.         $cfn=cfUTF8Decode($cfn,true,false,false);
  124.  
  125.         // If cover is requested and il must be searched for in file's directory
  126.         if(isset($folderImage))    {
  127.             require_once(INCLUDE_DIR.'explorerFunctions.php');
  128.             $coverCFN=efGetCoverFromFolder($cfn,$score);
  129.             // If no image found, return transparent (lightweight) image
  130.             return ($score>=0)?$coverCFN:cfAppDocRoot().'/gfx/v.gif';
  131.         }
  132.         return $cfn;
  133.     }
  134.  
  135.     // If called by /ext/audio.php
  136.     if(substr($encodedFilename,0,14)!='*resSpecific*/' || !is_numeric($cfn=cfFileWithoutExtension(substr($encodedFilename,14)))) return false;
  137.     if(!($db=@sqlite_open(cfAppResourceDir()."/music.db"))) return false;
  138.     $cfn=sqlite_single_query('SELECT Location FROM tracks WHERE id="'.$cfn.'"',$db);
  139.     sqlite_close($db);
  140.     if(cfFileExtension($cfn)!="mp3") $resampledAudioFile=true;
  141.     return cfUTF8Decode($cfn,true,false,false);
  142. }
  143.  
  144. /**
  145.  * @desc Folder music database: convert an "encoded" audio or cover filename into real filename
  146.  *         audio encoded format: *resourceBasePath* /1234.mp3 where 1234 is track id in database
  147.  *         cover encoded format: actual filename
  148.  * @param string $encodedFilename: encoded audio / cover filename
  149.  * @param bool $resampledAudioFile:
  150.  * @return string: real complete filename or false if not found / not allowed
  151.  */
  152. function mDBiTunesExtAccessFunction($encodedFilename,&$resampledAudioFile){
  153.     global $resampledAudioFile;
  154.  
  155.     // If called by /ext/image.php, verify that format is:
  156.     // (resourceBasePath)/(folder|embeded)XXXX.mp3
  157.     if(basename($_SERVER["SCRIPT_NAME"])=='image.php'){
  158.         if(cfFileRights($encodedFilename.'.itc2','download')) return $encodedFilename.'.itc2';
  159.         if(cfFileRights($encodedFilename.'.itc','download')) return $encodedFilename.'.itc';
  160.         return false;
  161.     }
  162.  
  163.     // If called by /ext/audio.php
  164.     if(substr($encodedFilename,0,14)!='*resSpecific*/' || !is_numeric($cfn=cfFileWithoutExtension(substr($encodedFilename,14)))) return false;
  165.     if(!($db=@sqlite_open(cfAppResourceDir()."/music.db"))) return false;
  166.     $cfn=sqlite_single_query('SELECT Location FROM tracks WHERE id="'.$cfn.'"',$db);
  167.     sqlite_close($db);
  168.     if(cfFileExtension($cfn)!="mp3") $resampledAudioFile=true;
  169.     return cfUTF8Decode(rawurldecode(str_replace("''","'",$cfn)),true,true,false);
  170. }
  171.  
  172.  
  173. /**
  174.  * @desc initialize data
  175.  *
  176.  */
  177. function initResourceData(){
  178.     // Define resource-vars that will be saved if user is singleUser
  179.     cfSetSavedRVar('displayType','groupBy','itemsPerPage','filterTd');
  180.  
  181.     // If resource already initialized, return
  182.     if(cfRGetVar('resourceParametersInitialized')) return;
  183.  
  184.     global $itemsPerPageList;
  185.     cfRSetVar('extensionFilter',false); // array of extensions of displayed files. Set to false if no filter on file extension
  186.     cfRSetVar('fileTypeFilter',array('audio'));// array of file types of displayed files. Set to false if no filter on file type (see mime_type.php for list of file types)
  187.     cfRSetVar('playlistDisplayed',true); // Show main playlist
  188.     cfRSetVar('needAudioInfo',true); // Indicate efGetDirContent to get id3 info
  189.     cfRInitVar('sortField','no'); // Files grouping
  190.     cfRSetVarArray('playlist'); // Reset main playlist
  191.  
  192.  
  193.     // extAccessFunction used to check file rights in /ext/audio.php
  194.     if(SOURCE_TYPE=='iTunes') cfRSetVar('extAccessFunction','global $resampledAudioFile; require_once(INCLUDE_DIR."musicDBFunctions.php"); return mDBiTunesExtAccessFunction($completeFilename,$resampledAudioFile);');
  195.     if(SOURCE_TYPE=='folder') cfRSetVar('extAccessFunction','global $resampledAudioFile; require_once(INCLUDE_DIR."musicDBFunctions.php"); return mDBFolderExtAccessFunction($completeFilename,$resampledAudioFile);');
  196.     if(SOURCE_TYPE=='winamp') cfRSetVar('extAccessFunction','global $resampledAudioFile; require_once(INCLUDE_DIR."musicDBFunctions.php"); return mDBWinampExtAccessFunction($completeFilename,$resampledAudioFile);');
  197.  
  198.  
  199.  
  200.     cfRInitVar('plAddAllShuffleSize',50); // Set "random play all libray" playlist size
  201.  
  202.     if(cfRGetVar('defaultOutputBitRate')) cfRSetVar('outputBitRate',cfRGetVar('defaultOutputBitRate'));
  203.     else cfRSetVar('outputBitRate',cfRGetVar('maxOutputBitRate'));
  204.  
  205.     if(cfRGetVar('defaultAudioPlayer')) cfRSetVar('audioPlayer',cfRGetVar('defaultAudioPlayer')); else cfRSetVar('audioPlayer','embeded');
  206.     cfRInitVar('efSortBy','name'); cfRInitVar('efSortOrder','asc');// sort files by ascending alphabetical order
  207.     cfRSetVar('useHTTPXmlRequest',true); // Use HTTPXmlRequest for communication between browser and server
  208.     if(cfRGetVar('resourceConfigured')) cfRSetVar('resourceParametersInitialized',true); // indicate that parameters have been initialized
  209.     else $errorMessageCritical=cfCaption('configErrorNotConfigured');
  210.  
  211.     cfRInitVar('displayType','displayCovers');
  212.     cfRInitVar('groupBy','Artist',array('Artist','Album','Playlist'));
  213.  
  214.     cfRInitVar('itemsPerPage',$itemsPerPageList[2]);
  215.     if(!in_array(cfRGetVar('itemsPerPage'),$itemsPerPageList)) cfRSetVar('itemsPerPage',$itemsPerPageList[2]);
  216.     cfRSetVar('displayOffset',0); // current offset from start of DB
  217.  
  218.     cfRSetVar('defaultAudioWindowHeight',DEFAULT_AUDIO_WINDOW_HEIGHT);
  219.     cfRSetVar('defaultPlaylistWindowHeight',DEFAULT_PLAYLIST_WINDOW_HEIGHT);
  220.  
  221.     cfRSetVar('subFoldersIncluded',true); // Allow access to subfolers of artwork base folder
  222.     cfRSetVar('efCurrentDirectory','*resSpecific*'); // Set current directory to *resSpecific* (for inclusion in comForm)
  223.     cfRSetVar('displayMultipleDownload',true); // unfold multiple download / playlist box
  224.  
  225.     // Browser specific parameters
  226.     if(!cfBGetVar('download'))    {
  227.         cfRSetVar('multipleDownloadAllowed',false);
  228.         cfRSetVar('downloadShortcut',false);
  229.         cfRSetVar('uploadAllowed',false);
  230.     }
  231.     if(!cfBGetVar('drag') || cfIsWII()) cfRSetVar('maxDragItems',0);
  232.  
  233.     // Adjust audio player to browser capabilities
  234.     if(!cfBGetVar(cfRGetVar('audioPlayer'))) cfRSetVar('audioPlayer',((cfBGetVar('flash'))?'flash':((cfBGetVar('wmp'))?'wmp':((cfBGetVar('qt'))?'qt':'qtReenc'))));
  235.  
  236.     // iPhone specific
  237.     if(cfBGetVar('name')=='iPhone') {
  238.         cfRSetVar('meta','<meta name="viewport" content="width=550, user-scalable=no,initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">');
  239.         cfRSetVar('onload','updateOrientation();scrollTo(0,1)');
  240.     }
  241.     if(cfBGetVar('noScrollDiv')) {
  242.         cfRSetVar('scrollMainFileTable',false);
  243.         cfRSetVar('mainFileTableToggleAllowed',false);
  244.     }
  245.  
  246.     // Inline player
  247.     if(!cfTGetVar('frames')) cfRSetVar('inlinePlayer',true);// Set inline player for frameless themes
  248.     if(cfBGetVar('standaloneMediaPlayer')) cfRSetVar('inlinePlayer',false);// Unset inline player for devices that doesn't support it
  249.     //if(cfBGetVar('name')=='Wii') cfRSetVar('inlinePlayer',false);// ... but don't set it inline for Wii...
  250.  
  251.     // Filter panel displayed (default)
  252.     cfRInitVar('filterTd',1);
  253.     cfRSetVar('parametersLoaded',true);
  254. }
  255.  
  256. /**
  257.  * @desc check if iTunes database has been modified and, if so parse it into :
  258.  *         - library table (general library parameters)
  259.  *         - tracks table (tracks data)
  260.  *         - playlists table (playlists id and name)
  261.  *         - playlistsTracks table (playlist id <-> track id)
  262.  * @param boolean $forceUpdate : set to true to force database update
  263.  * @return true if success
  264.  */
  265. function updateDBITunes($forceUpdate=false){
  266.     global $db;
  267.     $checkFilesExist=cfRGetVar('checkFilesExist');
  268.  
  269.     //$myMusicFolder=customQueryRegistry("HKEY_CURRENT_USER", "software\\Apple Computer, Inc.\\iTunes\\", "Win2KMyMusicFolder");
  270.  
  271.     $libraryFolder=cfRGetVar('musicITunesFolder');
  272.     if(substr($libraryFolder,-3)=='xml') $libraryFolder=dirname($libraryFolder);
  273.     $libraryFile=$libraryFolder.'/iTunes Music Library.xml';
  274.  
  275.     if(!file_exists($libraryFile)) outDisplayErrorPage(cfCaption('musicITunesFileNotFound','iTunes'));
  276.  
  277.     // Check library file hash to see if file has changed
  278.     $libraryFileHash=md5_file($libraryFile);
  279.     if(!$forceUpdate){
  280.         if(cfRGetVar('databaseUpdateMethod')=='manual') return true;
  281.         $lastLibraryFileHash=@sqlite_single_query('SELECT value FROM library WHERE property="File Hash"',$db);
  282.         if($lastLibraryFileHash==$libraryFileHash) return true;
  283.     }
  284.  
  285.     set_time_limit(0);
  286.     // Close session so other scripts aren't blocked
  287.     wSession_write_close();
  288.  
  289.     // Display "refresh" page
  290.     $msg='<script type="text/javascript">function uPB(nb,total){if(!total) {progressSetPerc("pBar",0);progressSetText("pBar","-");} else {progressSetPerc("pBar",100*nb/total);progressSetText("pBar",nb+" / "+total)}}</script><div id="action">'.cfCaption('musicRefreshingDatabase').'</div><br/><div id="progress" style="position:absolute;left:50%"><div style="position:relative;left:-150px">';
  291.     $msg.='<div class="eTC">'.outProgressBar(0,'300px','-','pBar').'</div>';
  292.     $msg.='</div></div>';
  293.  
  294.     echo outDisplayErrorPage($msg,array('icon'=>'clock','title'=>cfCaption('genWait')),false);
  295.     flush();
  296.  
  297.     // Create temporary DB
  298.     @sqlite_close($db);    openMusicDbTmp();
  299.  
  300.     // Parse iTunes XML file
  301.     $xml=simplexml_load_file($libraryFile);
  302.     $key=false;
  303.     $properties=array();
  304.     $artworks=array();
  305.     $query='BEGIN TRANSACTION;INSERT INTO library VALUES ("File Hash","'.$libraryFileHash.'");';
  306.     foreach ($xml->dict[0]->children() as $node){
  307.         // Playlist main paramters
  308.         if($node->getName()=='key') $key=(string)$node;
  309.         else {
  310.             if($key && $key!='Tracks' && $key!='Playlists') {
  311.                 $properties[$key]=(string)$node;
  312.                 $query.='INSERT INTO library VALUES ("'.$key.'","'.$properties[$key].'");';
  313.                 if($key=='Library Persistent ID'){
  314.                     // Search for covers (artworks)
  315.                     echo '<script type="text/javascript">uPB(0,0);dgi("action").innerHTML="'.cfCaption('musicRefreshingDatabase').'"</script>'."\n"; flush();
  316.                     // iTunes 8+
  317.                     if(is_dir($libraryFolder.'/Album Artwork/Cache/'.$properties['Library Persistent ID']))
  318.                         $artworks = getITunesArtworks($libraryFolder.'/Album Artwork/Cache/'.$properties['Library Persistent ID'],null,'itc2');
  319.                     // iTunes 7-
  320.                     elseif(is_dir($libraryFolder.'/Album Artwork/Local/'.$properties['Library Persistent ID']))
  321.                         $artworks = getITunesArtworks($libraryFolder.'/Album Artwork/Local/'.$properties['Library Persistent ID'],null,'itc');
  322.                     // Artwork not found
  323.                     else $artworks=array();
  324.                 }
  325.                 $key=false;
  326.             }
  327.             // Parse Tracks
  328.             elseif($key=='Tracks') {
  329.                 $nb=0;
  330.  
  331.                 $nbTracks=count($node->dict);
  332.                 echo '<script type="text/javascript">uPB(0,'.$nbTracks.');dgi("action").innerHTML="'.cfCaption('musicRefreshingDatabase').'"</script>'."\n"; flush();
  333.                 $lastRefresh=microtime(true);
  334.                 // Single track
  335.                 foreach ($node->dict as $trackNode){
  336.                     $key2=false;
  337.                     $track=emptyTrack();
  338.                     foreach ($trackNode->children() as $item){
  339.                         if($item->getName()=='key') $key2=(string)$item;
  340.                         elseif($key2) {
  341.                             $track[$key2]=sqlite_escape_string((string)$item);
  342.                             $key2=false;
  343.                         }
  344.                     }
  345.                     // File location (urlEncode(utf8))
  346.                     if(isset($track['Location'])) $track['Location']=substr($track['Location'],17); else $track['Location']='';
  347.  
  348.                     // If track ID and persistent ID found, process track
  349.                     if(($track['Persistent ID']) && ($track['Track ID'])){
  350.                         // Track year
  351.                         if($track['Year'] && !is_numeric($track['Year'])) $track['Year']=false;
  352.                         // Date added
  353.                         $td=$track['Date Added'];
  354.                         $track['Date Added']=mktime(substr($td,11,2),substr($td,14,2),substr($td,17,2),substr($td,5,2),substr($td,8,2),substr($td,0,4));
  355.                         // Artwork
  356.                         if(isset($artworks[$track['Persistent ID']])) $track['Artwork']=substr($artworks[$track['Persistent ID']],1).'/'.$properties['Library Persistent ID'].'-'.$track['Persistent ID'];
  357.  
  358.                         // Correct artist name for compilations
  359.                         if(isset($track['Album Artist'])) $track['Artist']=$track['Album Artist'];
  360.                         elseif(isset($track['Compilation'])) $track['Artist']=cfCaption('musicCompilation').' - '.$track['Album'];
  361.  
  362.                         // Insert into DB
  363.  
  364.                         if(!$checkFilesExist || file_exists(cfUTF8Decode(rawurldecode(str_replace("''","'",$track['Location'])),true,true,false))){
  365.                             $query.="INSERT INTO tracks VALUES ('".$track['Track ID'] . "','".$track['Name'] . "','".
  366.                             $track['Artist'] . "','".$track['Album'] . "','".$track['Track Number'] . "','".$track['Kind'] . "','".
  367.                             floor($track['Total Time']/1000) . "','".$track['Date Added'] . "','".$track['Comments'] . "','".
  368.                             $track['Persistent ID'] . "','".$track['Location'] . "','".$track['Year'] . "','".$track['Artwork'] . "');";
  369.                         }
  370.                         $nb++;
  371.                         if($nb%5000==0){
  372.                             $query.='COMMIT;';
  373.                             sqlite_query($query,$db);
  374.                             $query='BEGIN TRANSACTION;';
  375.                         }
  376.                         if(microtime(true)-$lastRefresh > 0.1){
  377.                             echo '<script type="text/javascript">uPB('.$nb.','.$nbTracks.');</script>'."\n"; flush();
  378.                             $lastRefresh=microtime(true);
  379.                         }
  380.                     }
  381.                 }
  382.                 if($query!='BEGIN TRANSACTION;') sqlite_query($query.'COMMIT;',$db);
  383.                 $query='BEGIN TRANSACTION;';
  384.                 echo '<script type="text/javascript">uPB(0,0);dgi("action").innerHTML="'.cfCaption('explorerAudioPlaylists').'"</script>'."\n"; flush();
  385.             }
  386.             // Parse playlists
  387.             elseif($key=='Playlists') {
  388.                 $query='BEGIN TRANSACTION;';
  389.                 $lastRefresh=microtime(true);
  390.                 $nbPlaylists=0;
  391.                 foreach ($node->children() as $playlistNode){
  392.                     $key2=false;
  393.                     $playlist=array('Name'=>false,'Playlist ID'=>false,'Playlist Type'=>false);
  394.                     foreach ($playlistNode->children() as $item){
  395.                         if($item->getName()=='key') {
  396.                             $key2=(string)$item;
  397.                             if($key2=='Music' || $key2=='Visible') $playlist['Playlist Type']=$key2;
  398.                         }
  399.                         elseif($key2 && $item->getName()!='array' && $item->getName()!='data') {
  400.                             $playlist[$key2]=sqlite_escape_string((string)$item);
  401.                             $key2=false;
  402.                         }
  403.                         // Parse playlist tracks
  404.                         elseif($key2=='Playlist Items' && $item->getName()=='array' && !$playlist['Playlist Type'] && $playlist['Playlist ID'] && $playlist['Name']) {
  405.                             $query.="INSERT INTO playlists VALUES ('".$playlist['Playlist ID']. "','".$playlist['Name']."');";
  406.                             foreach ($item->dict as $pltrack){
  407.                                 $query.="INSERT INTO playlistsTracks VALUES ('".$playlist['Playlist ID']. "','".(string)$pltrack->integer[0]."');";
  408.                             }
  409.                             $key2=false;
  410.                             $nbPlaylists++;
  411.                         }
  412.                     }
  413.                 }
  414.                 echo '<script type="text/javascript">uPB('.$nbPlaylists.','.$nbPlaylists.');dgi("action").innerHTML="'.cfCaption('explorerAudioPlaylists').'"</script>'."\n"; flush();
  415.                 if($query!='BEGIN TRANSACTION;') sqlite_query($query.'COMMIT;',$db);
  416.             }
  417.         }
  418.     }
  419.     //dbDisplayTable('tracks');exit;
  420.  
  421.     // Close DB
  422.     @sqlite_close($db);
  423.  
  424.     // Commit changes to actual DB
  425.     commitTmpMusicDB();
  426.  
  427.     // Reload page
  428.     echo '<form name="reloadForm"></form><script type="text/javascript">document.reloadForm.submit();</script>'."\n"; flush();
  429.     exit();
  430. }
  431.  
  432. /**
  433.  * @desc Update music database with audio files found in cfRGetVar('path') subfolders :
  434.  *         - library table (general library parameters)
  435.  *         - tracks table (tracks data)
  436.  *         - playlists table (playlists id and name)
  437.  *         - playlistsTracks table (playlist id <-> track id)
  438.  * @param boolean $forceUpdate : set to true to force database update
  439.  * @return true if success
  440.  */
  441. function updateDBFolders($forceUpdate=false){
  442.     if(!$forceUpdate) return true;
  443.  
  444.     require(INCLUDE_DIR.'fileInfoFunctions.php');
  445.     global $db;
  446.  
  447.     cfDebugSetBuffer('temp','Start updating base');
  448.  
  449.     set_time_limit(0);
  450.     // Close session so other scripts aren't blocked
  451.     wSession_write_close();
  452.  
  453.     // Display "refresh" page
  454.     $msg='<script type="text/javascript">function uPB(nb,total){if(!total) {progressSetPerc("pBar",0);progressSetText("pBar","-");} else {progressSetPerc("pBar",100*nb/total);progressSetText("pBar",nb+" / "+total)}} function uAct(c){dgi("action").innerHTML=c}</script><div id="action">'.cfCaption('musicRefreshingDatabase').'</div><br/><div id="progress" style="position:absolute;left:50%"><div style="position:relative;left:-150px">';
  455.     $msg.='<div class="eTC">'.outProgressBar(0,'300px','-','pBar').'</div>';
  456.     $msg.='</div></div>';
  457.  
  458.     echo outDisplayErrorPage($msg,array('icon'=>'unknown','title'=>cfCaption('genWait')),false);
  459.     flush();
  460.  
  461.     // Create temporary DB
  462.     @sqlite_close($db);    openMusicDbTmp();
  463.  
  464.     // Init SQLITE query
  465.     $query='BEGIN TRANSACTION;';
  466.  
  467.     // Set progress bar label
  468.     echo '<script type="text/javascript">uPB(0,0);dgi("action").innerHTML="'.cfCaption('musicRefreshingDatabase').'"</script>'."\n"; flush();
  469.  
  470.     $nb=0;
  471.     $playlistNb=0;
  472.     $artworkNames=array('front','cover','folder');
  473.     $dirs=array(cfRGetVar('path'));
  474.  
  475.     // Debug
  476.     if($debug=cfRGetVar('debug')) file_put_contents(cfAppRootDir().'/musicLog.txt','Audio library debug file');
  477.  
  478.     /**
  479.      * Browse base folder and subfolders for audio files, and list them into $dirs array
  480.      */
  481.     $nbTracks=0; $lastUpdate=microtime(true);
  482.     while ($dir=array_pop($dirs)) if(cfFileRights($dir,'state',true)=='authorized' && $dp=opendir($dir)){
  483.         $tmpQuery='';
  484.         while ($file=readdir($dp)) if($file!='.' && $file!='..'){
  485.             $file=$dir.'/'.$file;
  486.             // If dir, add to directories array
  487.             if(is_dir($file)) $dirs[]=$file;
  488.             // If audio file, process
  489.             elseif(efFileType($file)=='audio') $nbTracks++;
  490.         }
  491.         if((microtime(true)-$lastUpdate>0.500)){
  492.             echo '<script type="text/javascript">uPB(0,'.$nbTracks.');</script>'."\n"; flush();
  493.             $lastUpdate=microtime(true);
  494.         }
  495.         closedir($dp);
  496.     }
  497.  
  498.  
  499.     /**
  500.      * Process directories
  501.      */
  502.     $dirs=array(cfRGetVar('path')); $lastUpdate=microtime(true);
  503.     while ($dir=array_pop($dirs)) if(cfFileRights($dir,'state',true)=='authorized' && $dp=opendir($dir)){
  504.         $tmpQuery='';
  505.         // Look for audio files in directory
  506.         while ($file=readdir($dp)) if($file!='.' && $file!='..'){
  507.             $file=$dir.'/'.$file;
  508.             // If dir, add to directories array
  509.             if(is_dir($file)) $dirs[]=$file;
  510.             // If audio file, process
  511.             elseif(efFileType($file)=='audio' && cfFileRights($file,'download',false)){
  512.                 // Debug
  513.                 if($debug) cfAppendTextToFile('Process file '.$file,cfAppRootDir().'/musicLog.txt');
  514.  
  515.                 /**
  516.                  * Playlist
  517.                  */
  518.                 if(in_array(cfFileExtension($file),$_ENV['supportedPlaylists'])){
  519.                     // Parse playlist
  520.                     $playlistTracks=fiParsePlaylistFile($file,true,false);
  521.  
  522.                     // If not empty
  523.                     if(count($playlistTracks)) {
  524.                         // Insert into DB
  525.                         $tmpQuery.="INSERT INTO playlists VALUES ('".$playlistNb. "','".sqlite_escape_string(cfUTF8Encode(cfFileWithoutExtension(basename(str_replace(chr(0),'',$file)))))."');";
  526.                         // Insert tracks filenames into temporary DB for further matching with track ids.
  527.                         foreach ($playlistTracks as $plT){
  528.                             $tmpQuery.="INSERT INTO playlistsTemp VALUES ('".$playlistNb. "','".sqlite_escape_string(cfUTF8Encode(str_replace(chr(0),'',$plT['cfn'])))."');";
  529.                         }
  530.                         $playlistNb++;
  531.                     }
  532.                 }
  533.  
  534.                 /**
  535.                  * Songs
  536.                  */
  537.                 else{
  538.                     // Debug
  539.                     if($debug) echo '<script type="text/javascript">uAct("'.cfUTF8Encode($file).'");</script>'."\n"; flush();
  540.  
  541.                     //cfDebugUpdateBuffer('process '.$file);
  542.  
  543.                     // Get tags
  544.                     $track=@efGetAudioInfo($file,false,array('noCache'));
  545.  
  546.                     if(!isset($track['artist'])){
  547.                         $track=array('artist'=>false,'album'=>false,'track'=>false,'year'=>false,'genre'=>false,'comment'=>false);
  548.                     }
  549.                     $track['Persistent ID']=$nb;
  550.                     $track['Track ID']=$nb; // Mettre un hash de $file α cause des playlists
  551.                     $track['Total Time']=((isset($track['length']) && is_numeric($track['length']))?floor($track['length']/1000):0);
  552.                     $track['Date Added']=@filemtime($file);
  553.                     $track['Location']=$file;
  554.  
  555.                     // Look for artwork in embeded ID3 tags
  556.                     if(isset($track['APIC']))
  557.                         $track['Artwork']='embeded'.$nb.'.mp3';
  558.                     else
  559.                         $track['Artwork']='folder'.$nb.'.mp3';
  560.  
  561.                     $track['Kind']=$track['genre'];
  562.                     $track['Track Number']=$track['track'];
  563.                     $track['artist']=trim(str_replace(chr(0),'',$track['artist']));
  564.                     $track['album']=trim(str_replace(chr(0),'',$track['album']));
  565.  
  566.                     // SQLite protect and utf-8 encode data
  567.                     foreach ($track as $key=>$value) $track[$key]=sqlite_escape_string(cfUTF8Encode($value,false,false));
  568.                     $tmpQuery.="INSERT INTO tracks VALUES ('".$track['Track ID'] . "','".str_replace(chr(0),'',$track['title']). "','".
  569.                     $track['artist'] . "','".$track['album'] . "','".$track['Track Number'] . "','".$track['Kind'] . "','".
  570.                     $track['Total Time'] . "','".$track['Date Added'] . "','".$track['comment'] . "','".
  571.                     $track['Persistent ID'] . "','".$track['Location'] . "','".$track['year'] . "','".$track['Artwork'] . "');";
  572.  
  573.                     $nb++;
  574.                 }
  575.             }
  576.             elseif ($debug && efFileType($file)=='audio')
  577.                 cfAppendTextToFile('Unauthorized file '.$file,cfAppRootDir().'/musicLog.txt');
  578.  
  579.             if((microtime(true)-$lastUpdate>0.500)){
  580.                 echo '<script type="text/javascript">uPB('.$nb.','.$nbTracks.');</script>'."\n"; flush();
  581.                 $lastUpdate=microtime(true);
  582.             }
  583.         }
  584.         if((microtime(true)-$lastUpdate>0.500)){
  585.             echo '<script type="text/javascript">uPB('.$nb.','.$nbTracks.');</script>'."\n"; flush();
  586.             $lastUpdate=microtime(true);
  587.         }
  588.         if($tmpQuery) $query.=$tmpQuery;
  589.         closedir($dp);
  590.     }
  591.  
  592.     sqlite_query($query.'COMMIT;',$db);
  593.     // Generate playlistsTracks table (link tracks filenames with tracks ids)
  594.     echo '<script type="text/javascript">uPB(0,0);dgi("action").innerHTML="'.cfCaption('explorerAudioPlaylists').'"</script>'."\n"; flush();
  595.     sqlite_query('INSERT INTO playlistsTracks SELECT tmp.PlaylistID as PlaylistID, tracks.id as TrackId FROM playlistsTemp AS tmp INNER JOIN tracks ON tmp.TrackFilename=tracks.Location',$db);
  596.  
  597.     //dbDisplayTable('tracks');exit;
  598.  
  599.     // Close DB
  600.     @sqlite_close($db);
  601.  
  602.     if($debug) cfAppendTextToFile('Commit to DB...',cfAppRootDir().'/musicLog.txt');
  603.  
  604.     // Commit changes to actual DB
  605.     commitTmpMusicDB();
  606.  
  607.     if($debug) cfAppendTextToFile('All OK, reload page',cfAppRootDir().'/musicLog.txt');
  608.  
  609.     // Reload page
  610.     echo '<form name="reloadForm"></form><script type="text/javascript">document.reloadForm.submit();</script>'."\n"; flush();
  611.     exit();
  612. }
  613.  
  614. /**
  615.  * @desc Update music database with audio files found in winamp database
  616.  *         - library table (general library parameters)
  617.  *         - tracks table (tracks data)
  618.  *         - playlists table (playlists id and name)
  619.  *         - playlistsTracks table (playlist id <-> track id)
  620.  * @param boolean $forceUpdate : set to true to force database update
  621.  * @return true if success
  622.  */
  623. function updateDBWinamp($forceUpdate=false){
  624.     if(!$forceUpdate) return true;
  625.     /**
  626.      * Read One Winamp Database field
  627.      */
  628.     function readWinampDatabaseField(&$fp){
  629.         static $nnb;
  630.         $fieldHeader=array();
  631.         $fieldHeader['nextFieldOffset']=1;
  632.         $ret=array();
  633.         while($fieldHeader['nextFieldOffset']){
  634.             if(!($rawHeader=fread($fp,14))) return $ret;
  635.  
  636.             $fieldHeader=unpack('Cid/CfieldType/VfieldDataSize/VnextFieldOffset/VprevFieldOffset',$rawHeader);
  637.  
  638.             $data=fread($fp,$fieldHeader['fieldDataSize']);
  639.  
  640.             switch ($fieldHeader['fieldType']){
  641.                 case FIELD_COLUMN:
  642.                     $data=substr($data,3);
  643.  
  644.                     break;
  645.                 case FIELD_STRING:
  646.                 case FIELD_STRINGUTF8:
  647.                     $data=str_replace("\0",'',substr($data,4));
  648.                     break;
  649.                 case FIELD_INTEGER:
  650.                 case FIELD_DATETIME:
  651.                 case FIELD_LENGTH:
  652.                     $data=unpack('Vint',$data);
  653.                     $data=$data['int'];
  654.                     break;
  655.                 case FIELD_BOOLEAN:
  656.                     if(!$data) $data=false; else $data=true;
  657.                     break;
  658.                 case FIELD_BINARY:
  659.                     $data=substr($data,2);
  660.                     $data=$data['int'];
  661.                     break;
  662.                 case FIELD_GUID:
  663.                     break;
  664.                 case FIELD_FLOAT:
  665.                     $data=unpack('ffloat',$data);
  666.                     $data=$data['float'];
  667.                     break;
  668.                 default:
  669.                     $data='?';
  670.             }
  671.             $ret[$fieldHeader['id']]=$data;
  672.         }
  673.         return $ret;
  674.     }
  675.  
  676.     /**
  677.      * Count winamp database number of records
  678.      */
  679.     function countWinampDatabaseField(&$fp){
  680.         $nbTracks=0;
  681.  
  682.         while(!feof($fp)){
  683.             $fieldHeader=array();
  684.             $fieldHeader['nextFieldOffset']=1;
  685.             while($fieldHeader['nextFieldOffset']){
  686.                 if(!($rawHeader=fread($fp,14))) {$nbTracks--; break;} // EOF
  687.                 $fieldHeader=unpack('Cid/CfieldType/VfieldDataSize/VnextFieldOffset/VprevFieldOffset',$rawHeader);
  688.                 fseek($fp,$fieldHeader['fieldDataSize'],SEEK_CUR);
  689.             }
  690.             $nbTracks++;
  691.         }
  692.         return $nbTracks;
  693.     }
  694.  
  695.     global $db;
  696.  
  697.     $lookForEmbededCover=cfRGetVar('lookForEmbededCover');
  698.  
  699.     require(INCLUDE_DIR.'miscFunctions.php');
  700.  
  701.     // Winamp Library file
  702.     $libraryFile=cfRGetVar('musicWinampDatabaseFile');
  703.  
  704.     if(!file_exists($libraryFile)) outDisplayErrorPage(cfCaption('musicITunesFileNotFound','Winamp').'<br>('.$libraryFile.')');
  705.  
  706.     // Check library file hash to see if file has changed
  707.     $libraryFileHash=md5_file($libraryFile);
  708.     if(!$forceUpdate){
  709.         if(cfRGetVar('databaseUpdateMethod')=='manual') return true;
  710.         $lastLibraryFileHash=@sqlite_single_query('SELECT value FROM library WHERE property="File Hash"',$db);
  711.         if($lastLibraryFileHash==$libraryFileHash) return true;
  712.     }
  713.  
  714.     set_time_limit(0);
  715.     // Close session so other scripts aren't blocked
  716.     wSession_write_close();
  717.  
  718.     // Display "refresh" page
  719.     $msg='<script type="text/javascript">function uPB(nb,total){if(!total) {progressSetPerc("pBar",0);progressSetText("pBar","-");} else {progressSetPerc("pBar",100*nb/total);progressSetText("pBar",nb+" / "+total)}}</script><div id="action">'.cfCaption('musicRefreshingDatabase').'</div><br/><div id="progress" style="position:absolute;left:50%"><div style="position:relative;left:-150px">';
  720.     $msg.='<div class="eTC">'.outProgressBar(0,'300px','-','pBar').'</div>';
  721.     $msg.='</div></div>';
  722.  
  723.     echo outDisplayErrorPage($msg,array('icon'=>'unknown','title'=>cfCaption('genWait')),false);
  724.     flush();
  725.  
  726.     // Create temporary database
  727.     @sqlite_close($db);    openMusicDbTmp();
  728.  
  729.     // Init SQLITE query
  730.     $query='BEGIN TRANSACTION;INSERT INTO library VALUES ("File Hash","'.$libraryFileHash.'");';
  731.     $tmpQuery='';
  732.  
  733.     // Set progress bar label
  734.     echo '<script type="text/javascript">uPB(0,0);dgi("action").innerHTML="'.cfCaption('musicRefreshingDatabase').'"</script>'."\n"; flush();
  735.  
  736.     $nb=0;
  737.     $playlistNb=0;
  738.     $artworkNames=array('front','cover','folder');
  739.  
  740.  
  741.     /**
  742.      * Parse Winamp database
  743.      */
  744.     $fp=fopen($libraryFile,'rb');
  745.     fseek($fp,8,SEEK_SET);
  746.  
  747.     // Read columns (array columnId => column name)
  748.     $columns=readWinampDatabaseField($fp);
  749.  
  750.     // Translate from winamp names to local names
  751.     foreach ($columns as $id=>$name){
  752.         if($name=='length') $columns[$id]='Total Time';
  753.         if($name=='filename') $columns[$id]='Location';
  754.         if($name=='genre') $columns[$id]='Kind';
  755.         if($name=='trackno') $columns[$id]='Track Number';
  756.     }
  757.  
  758.     // Skip indexes
  759.     readWinampDatabaseField($fp);
  760.  
  761.     // Read number of entries
  762.     $pos=ftell($fp);
  763.     $nbTracks=countWinampDatabaseField($fp);
  764.     fseek($fp,$pos,SEEK_SET);
  765.  
  766.     // Read tracks
  767.     $nb=0;
  768.     $tracks=array();
  769.  
  770.     $unknownArtist=cfCaption('explorerAudioUnknownArtist');
  771.     $unknownAlbum=cfCaption('explorerAudioUnknownAlbum');
  772.     $unknownTitle=cfCaption('explorerAudioUnknownTitle');
  773.  
  774.     $lastUpdate=microtime(true);
  775.     while (!feof($fp)) {
  776.         // Read track from source DB
  777.         $data=readWinampDatabaseField($fp);
  778.  
  779.         // Set default values
  780.         $track=array();
  781.         $track['Persistent ID']=$nb;
  782.         $track['Track ID']=$nb;
  783.         $track['Total Time']=0;
  784.  
  785.         $track['artist']=$unknownArtist;
  786.         $track['album']=$unknownAlbum;
  787.         $track['title']=$unknownTitle;
  788.         $track['comment']='';
  789.         $track['year']='1972';
  790.         $track['Kind']=0;
  791.         $track['Track Number']=0;
  792.  
  793.         // Convert columns ID to columns names
  794.         foreach ($data as $id=>$value) $track[$columns[$id]]=$value;
  795.         // Skip if location not found
  796.         if(!isset($track['Location']) /*|| !file_exists($track['Location'])*/) continue;
  797.  
  798.         if(!isset($track['Date Added'])) $track['Date Added']=@filemtime($file);
  799.  
  800.         // Group by akbum artist
  801.         if(isset($track['albumartist'])) $track['artist']=$track['albumartist'];
  802.         //if(isset($track['Total Time'])) $track['Total Time']*=1000;
  803.  
  804.         // Check for cover
  805.         if($lookForEmbededCover){
  806.             $cover=new id3V2Ext($track['Location'],'APIC');
  807.             if($cover->hasImage()) $track['Artwork']='embeded'.$nb.'.mp3';
  808.             else $track['Artwork']='folder'.$nb.'.mp3';
  809.         }
  810.         else $track['Artwork']='folder'.$nb.'.mp3';
  811.  
  812.  
  813.         // Insert in DB
  814.         foreach ($track as $key=>$value) $track[$key]=sqlite_escape_string(cfUTF8Encode($value,false,false));
  815.         $tmpQuery.="INSERT INTO tracks VALUES ('".$track['Track ID'] . "','".$track['title']. "','".
  816.         $track['artist'] . "','".$track['album'] . "','".$track['Track Number'] . "','".$track['Kind'] . "','".
  817.         $track['Total Time'] . "','".$track['Date Added'] . "','".$track['comment'] . "','".
  818.         $track['Persistent ID'] . "','".$track['Location'] . "','".$track['year'] . "','".$track['Artwork'] . "');";
  819.  
  820.         // Also save in an array (name=>id) so mapping can be done for playlists
  821.         $tracks[$track['Location']]=$track['Track ID'];
  822.  
  823.         $nb++;
  824.  
  825.         // Update progress bar
  826.         if((microtime(true)-$lastUpdate>0.500)){
  827.             echo '<script type="text/javascript">uPB('.$nb.','.$nbTracks.');</script>'."\n"; flush();
  828.             $query.=$tmpQuery; $tmpQuery='';
  829.             if($query!='BEGIN TRANSACTION;') {
  830.                 sqlite_query($query.'COMMIT;',$db);
  831.                 $query='BEGIN TRANSACTION;';
  832.             }
  833.             $lastUpdate=microtime(true);
  834.         }
  835.     }
  836.  
  837.     fclose($fp);
  838.  
  839.     echo '<script type="text/javascript">uPB('.$nbTracks.','.$nbTracks.');</script>'."\n"; flush();
  840.  
  841.     // Save to database
  842.     if($query!='BEGIN TRANSACTION;') sqlite_query($query.$tmpQuery.'COMMIT;',$db);
  843.  
  844.  
  845.     /**
  846.      * Parse playlists
  847.      */
  848.     $playlists=array();
  849.     $query='BEGIN TRANSACTION;';
  850.     echo '<script type="text/javascript">uPB(0,0);dgi("action").innerHTML="'.cfCaption('explorerAudioPlaylists').'"</script>'."\n"; flush();
  851.     if(file_exists(dirname($libraryFile).'/playlists.xml')){
  852.         // Parse main XML playlist file
  853.         $domDocument = new DOMDocument;
  854.         if((@$result=$domDocument->load(dirname($libraryFile).'/playlists.xml'))){
  855.             $root=$domDocument->documentElement;
  856.             $encoding=$domDocument->actualEncoding;
  857.             if(($pl=$root->getElementsByTagName('playlist'))){
  858.                 for($i=0;$i<$pl->length;$i++){
  859.                     $playlist=$pl->item($i);
  860.                     if($playlist->hasAttribute('filename') /* && $playlist->hasAttribute('id')*/ && $playlist->hasAttribute('title') && @file_exists(dirname($libraryFile).'/'.$playlist->getAttribute('filename'))){
  861.                         $playlists[$i]=array(
  862.                             'filename'=>$playlist->getAttribute('filename'),
  863.                             'title'=>sqlite_escape_string($playlist->getAttribute('title')));
  864.                     }
  865.                 }
  866.             }
  867.             $nbPlaylists=count($playlists);
  868.             echo '<script type="text/javascript">uPB(0,'.$nbPlaylists.');dgi("action").innerHTML="'.cfCaption('explorerAudioPlaylists').'"</script>'."\n"; flush();
  869.  
  870.             // Read individual playlists
  871.             $lastUpdate=microtime(true);
  872.             $nb=0;
  873.             foreach ($playlists as $id=>$playlist){
  874.                 $lines=explode("\n",@file_get_contents(dirname($libraryFile).'/'.$playlist['filename']));
  875.                 $playlistsTracks=array();
  876.                 foreach ($lines as $line) {
  877.                     $line=trim($line);
  878.                     if(substr($line,0,1)!='#' && isset($tracks[$line])) $playlistsTracks[]=$tracks[$line];
  879.                 }
  880.                 // Non-empty playlist: add to database
  881.                 if(count($playlistsTracks)) {
  882.                     $query.="INSERT INTO playlists VALUES ('".$id. "','".$playlist['title']."');";
  883.                     foreach ($playlistsTracks as $trackId){
  884.                         $query.="INSERT INTO playlistsTracks VALUES ('".$id. "','".$trackId."');";
  885.                     }
  886.                 }
  887.                 else $nbPlaylists--;
  888.                 $nb++;
  889.                 // Update progress bar & commit SQL requests
  890.                 if((microtime(true)-$lastUpdate>0.500)){
  891.                     echo '<script type="text/javascript">uPB('.$nb.','.$nbPlaylists.');dgi("action").innerHTML="'.cfCaption('explorerAudioPlaylists').'"</script>'."\n"; flush();
  892.                     $lastUpdate=microtime(true);
  893.                     if($query!='BEGIN TRANSACTION;') {
  894.                         sqlite_query($query.'COMMIT;',$db);
  895.                         $query='BEGIN TRANSACTION;';
  896.                     }
  897.                 }
  898.             }
  899.             unset($lines);
  900.         }
  901.         unset($domDocument);
  902.     }
  903.     // Update progress bar & commit SQL requests
  904.     echo '<script type="text/javascript">uPB('.$nbPlaylists.','.$nbPlaylists.');dgi("action").innerHTML="'.cfCaption('explorerAudioPlaylists').'"</script>'."\n";
  905.     flush();
  906.     if($query!='BEGIN TRANSACTION;') sqlite_query($query.'COMMIT;',$db);
  907.  
  908.     //dbDisplayTable('tracks');exit;
  909.  
  910.     // Close DB
  911.     @sqlite_close($db);
  912.  
  913.     // Commit changes to actual DB
  914.     commitTmpMusicDB();
  915.  
  916.     // Reset time limit
  917.     set_time_limit(ini_get('max_execution_time'));
  918.  
  919.     // Reload page
  920.     echo '<form name="reloadForm"></form><script type="text/javascript">document.reloadForm.submit();</script>'."\n"; flush();
  921.     exit();
  922. }
  923.  
  924. /**
  925.  * @desc Debug dump music database content
  926.  *
  927.  */
  928. function mDBDumpBase(){
  929.     global $db;
  930.     dbDisplayTable('playlists');
  931.     dbDisplayTable('playlistsTracks');
  932.     dbDisplayTable('tracks');
  933. }
  934.  
  935. /**
  936.  * @desc protect " with a \ for inclusion in javascript arrays
  937.  *
  938.  * @param string $string : string to protect
  939.  * @return string : protected string
  940.  */
  941. function esc($string){return addslashes($string);}
  942.  
  943. /**
  944.  * @desc protect " with a \ and limit size for inclusion in select
  945.  *
  946.  * @param string $string : string to protect
  947.  * @return string : protected string
  948.  */
  949. function esc2($string){return addslashes(cfStrTruncate($string,35));}
  950.  
  951. /**
  952.  * @desc open sqlite database and create tables if needed
  953.  *
  954.  * @return boolean : true if DB has been created, false if DB already created
  955.  *
  956.  */
  957. function openMusicDb($dbFilename=false){
  958.     global $db;
  959.     // Open DB
  960.     if($dbFilename) $db=@sqlite_open($dbFilename);
  961.     else $db=@sqlite_open(cfAppResourceDir().'/music.db');
  962.  
  963.     if($db==false) die("DB creation error");
  964.  
  965.     // Create playlists table if not existing
  966.     if(!sqlite_table_exists($db,'library')) {
  967.         $createDB=true;
  968.         @sqlite_query('CREATE TABLE library (property TEXT, value TEXT)',$db);
  969.     }
  970.     else $createDB=false;
  971.  
  972.     // Create tracks table if not existing
  973.     if(!sqlite_table_exists($db,'tracks')) {
  974.         @sqlite_query('CREATE TABLE tracks ('.
  975.         'id PRIMARY KEY, '.
  976.         'Name TEXT, '.
  977.         'Artist TEXT, '.
  978.         'Album TEXT, '.
  979.         'TrackNumber INTEGER, '.
  980.         'Kind TEXT, '.
  981.         'TotalTime INTEGER, '.
  982.         'DateAdded INTEGER, '.
  983.         'Comments TEXT, '.
  984.         'PersistentID TEXT, '.
  985.         'Location TEXT, '.
  986.         'Year INTEGER, '.
  987.         'Artwork TEXT'.
  988.         ')',$db);
  989.     }
  990.  
  991.     // Create playlists table if not existing
  992.     if(!sqlite_table_exists($db,'playlists')) @sqlite_query('CREATE TABLE playlists (PlaylistID PRIMARY KEY, Name TEXT)',$db);
  993.  
  994.     // Create playlists temporary table if not existing
  995.     if(!sqlite_table_exists($db,'playlistsTemp')) @sqlite_query('CREATE TABLE playlistsTemp (PlaylistID INTEGER, TrackFilename TEXT)',$db);
  996.  
  997.     // Create playlists <-> tracks table if not existing
  998.     //if(!sqlite_table_exists($db,'playlistsTracks')) @sqlite_query('CREATE TABLE playlistsTracks (PlaylistID INTEGER, TrackID INTEGER,  PRIMARY KEY(PlaylistID, TrackID))',$db);
  999.     if(!sqlite_table_exists($db,'playlistsTracks')) @sqlite_query('CREATE TABLE playlistsTracks (PlaylistID INTEGER, TrackID INTEGER)',$db);
  1000.  
  1001.     return $createDB;
  1002. }
  1003.  
  1004. /**
  1005.  * @desc Open temporary database used for generation
  1006.  *
  1007.  * @return boolean result
  1008.  */
  1009. function openMusicDbTmp(){
  1010.     global $db;
  1011.     // Unlink previous temporary database
  1012.     if(file_exists(cfAppResourceDir().'/music.db.tmp') && !@unlink(cfAppResourceDir().'/music.db.tmp')){
  1013.         // Or empty table if unlink impossible
  1014.         $db==@sqlite_open(cfAppResourceDir().'/music.db');
  1015.         sqlite_single_query('BEGIN TRANSACTION;DELETE FROM library;DELETE FROM tracks;DELETE FROM playlists;DELETE FROM playlistsTracks;DELETE FROM playlistsTemp;COMMIT',$db);
  1016.         return true;
  1017.     }
  1018.     // Create new temporary database
  1019.     return openMusicDb(cfAppResourceDir().'/music.db.tmp');
  1020. }
  1021.  
  1022. /**
  1023.  * @desc Commit temporary db
  1024.  *
  1025.  */
  1026. function commitTmpMusicDB(){
  1027.     @unlink(cfAppResourceDir().'/music.db');
  1028.     @rename(cfAppResourceDir().'/music.db.tmp',cfAppResourceDir().'/music.db');
  1029. }
  1030.  
  1031. /**
  1032.  * @desc recurse-read iTunes subfolders for artworks (.itc/.itc2)
  1033.  *
  1034.  * @param string $baseFolder: top folder
  1035.  * @param string $folder: current folder
  1036.  * @param string $extension: cover files extension (.itc/.itc2)
  1037.  * @return array (TrackId=>$baseFolder's subfolder, ...)
  1038.  */
  1039. function getITunesArtworks($baseFolder,$folder=null,$extension='itc'){
  1040.     if($folder==null) $folder=$baseFolder;
  1041.     $artworks=array();
  1042.     $folders=cfGlob($folder.'/*');
  1043.     foreach ($folders as $single){
  1044.         if(is_dir($single)) $artworks=array_merge($artworks,getITunesArtworks($baseFolder,$folder.'/'.basename($single),$extension));
  1045.     }
  1046.     foreach (cfGlob($folder.'/*.'.$extension) as $single){
  1047.         $PID=cfFileWithoutExtension(basename($single));
  1048.         $PID=substr($PID,strpos($PID,'-')+1);
  1049.         $artworks[$PID]=substr($folder,strlen($baseFolder));
  1050.     }
  1051.     return $artworks;
  1052. }
  1053.  
  1054. /**
  1055.  * @desc return empty track array
  1056.  *
  1057.  * @return array
  1058.  */
  1059. function emptyTrack(){
  1060.     return array('Track ID'=>false,
  1061.     'Name'=>false,
  1062.     'Artist'=>false,
  1063.     'Album'=>false,
  1064.     'Track Number'=>false,
  1065.     'Kind'=>false,
  1066.     'Total Time'=>false,
  1067.     'Date Added'=>false,
  1068.     'Comments'=>false,
  1069.     'Persistent ID'=>false,
  1070.     'Location'=>false,
  1071.     'Year'=>false,
  1072.     'Artwork'=>false);
  1073. }
  1074.  
  1075. /**
  1076.  * @desc process POST commands
  1077.  *
  1078.  */
  1079. function mDBprocessPost(){
  1080.     global $db;
  1081.     global $itemsPerPageList;
  1082.  
  1083.  
  1084.     cfAsyncXMLChangePhpVarValue('filterTd'); // process hide / show filter panel
  1085.  
  1086.  
  1087.     // If no POST command sent, return
  1088.     if(!isset($_POST['data3'])) return;
  1089.     $action=$_POST['data3'];
  1090.  
  1091.     // Strip backslashes in artist / album name
  1092.     if(isset($_POST['data2'])) $_POST['data2']=stripcslashes($_POST['data2']);
  1093.     if(isset($_POST['data4'])) $_POST['data4']=stripcslashes($_POST['data4']);
  1094.  
  1095.     /**
  1096.      * Filter parameters
  1097.      */
  1098.     if($action=='changeGroupBy' || $action=='changeDisplayType' || $action=='search' || $action=='changeItemsPerPage' || $action=='refreshDisplay' || $action=='changeOffset'){
  1099.         // Send async response header
  1100.         cfAsyncHeader();
  1101.  
  1102.         // Refresh selection area content
  1103.         //if($action=='refreshDisplay') asyncRefreshDisplay();
  1104.         // Change display type
  1105.         if($action=='changeDisplayType' && isset($_POST['data2'])) {
  1106.             if(in_array($_POST['data2'],array('displayCompact','displayFull','displayCovers'))) cfRSetVar('displayType',$_POST['data2']);
  1107.         }
  1108.         // Search filter
  1109.         if($action=='search' && isset($_POST['data2'])) {
  1110.             if(!isset($_POST['data4']) || !$_POST['data4']) echo cfAsyncXMLJSaction('dgi("searchInput").value="";');
  1111.             cfRSetVar('groupBy','Search');
  1112.             if(substr($_POST['data2'],0,1)=='=') $_POST['data2']=substr($_POST['data2'],1); // if search arg starts by '=', don't add wildcards
  1113.             elseif(strpos($_POST['data2'],'*')===false && strpos($_POST['data2'],'?')===false) $_POST['data2']='*'.$_POST['data2'].'*'; // else if no wildcard or ?; add * at start and end of search
  1114.             cfRSetVar('searchValue',str_replace('*','%',str_replace('?','_',($_POST['data2']))));
  1115.         }
  1116.         // Change grouping option
  1117.         if($action=='changeGroupBy' && isset($_POST['data2'])) {
  1118.             if($_POST['data2']!=cfRGetVar('groupBy')) cfRSetVar('displayOffset',0);
  1119.             if($_POST['data2']=='Artist' || $_POST['data2']=='Album' || $_POST['data2']=='Year' || $_POST['data2']=='Playlist')
  1120.                 cfRSetVar('groupBy',$_POST['data2']);
  1121.         }
  1122.         // Change number of items displayed per page
  1123.         if($action=='changeItemsPerPage' && isset($_POST['data2']))
  1124.             if(in_array($_POST['data2'],$itemsPerPageList))    cfRSetVar('itemsPerPage',$_POST['data2']);
  1125.  
  1126.         // Change current offset
  1127.         if($action=='changeOffset' && isset($_POST['data2']))
  1128.             cfRSetVar('displayOffset', $_POST['data2']*cfRGetVar('itemsPerPage'));
  1129.  
  1130.         /**
  1131.          * Refresh display
  1132.          */
  1133.         // If mobile, we're on options page, don't update anything
  1134.         if(cfIsMobile() && ($action=='changeDisplayType' || $action=='changeItemsPerPage')) {}
  1135.         // Regular browser: update view
  1136.         else asyncRefreshDisplay();
  1137.  
  1138.         echo cfAsyncFooter();
  1139.         @sqlite_close($db);
  1140.         exit();
  1141.     }
  1142.     // Show artist / album / playlist detail
  1143.     // $_POST['data1'] : Album name ('Album' detail only);
  1144.     // $_POST['data2'] : Artist name or playlist ID
  1145.     // $_POST['data4'] : destination DOM id
  1146.     // $_POST['data5'] : detail type ('Artist', 'Album' or 'Playlist')
  1147.     elseif(isset($action) && $action=='showDetail' && isset($_POST['data2']) && isset($_POST['data4']) && isset($_POST['data5'])){
  1148.         // Send async response header
  1149.         cfAsyncHeader();
  1150.         switch ($_POST['data5']){
  1151.             case 'Artist':
  1152.                 showDetailArtist(stripslashes($_POST['data2']),$_POST['data4']);
  1153.                 break;
  1154.             case 'Album':
  1155.                 showDetailAlbum(stripslashes($_POST['data1']),stripslashes($_POST['data2']),$_POST['data4']);
  1156.                 break;
  1157.             case 'Playlist':
  1158.                 showDetailPlaylist(stripslashes($_POST['data2']),$_POST['data4']);
  1159.                 break;
  1160.         }
  1161.         echo cfAsyncFooter();
  1162.         @sqlite_close($db);
  1163.         exit();
  1164.     }
  1165.  
  1166.  
  1167.  
  1168.     /**
  1169.      * Send playlist to browser as M3U file
  1170.      */
  1171.     if($action=='view' && @$_POST['data4']=='forceM3U'){
  1172.         efSendM3UFile('*playlist*.m3u');
  1173.         exit;
  1174.     }
  1175.  
  1176.  
  1177.     /**
  1178.      * Inline play
  1179.      * $_POST['data2'] format :
  1180.      *     - track number,
  1181.      *  - or *resSpecific* /tracknumber.mp3,
  1182.      *  - or *playlist*.m3u,
  1183.      *  - or artist/"artist name"
  1184.      *  - or album/"artist name"/"album name"
  1185.      */
  1186.     elseif(cfRGetVar('inlinePlayer') && isset($_POST['data2']) && $action=='view'){
  1187.         // Send async response header
  1188.         cfAsyncHeader();
  1189.  
  1190.         require(INCLUDE_DIR.'viewFunctions.php');
  1191.         $passedFile=rawurldecode($_POST['data2']);
  1192.         if(substr($passedFile,0,14)=='*resSpecific*/') $passedFile=cfFileWithoutExtension(substr($passedFile,14));
  1193.  
  1194.         // Play inline playlist
  1195.         if($passedFile=='*playlist*.m3u') {
  1196.             // Generate shuffled playlist if needed
  1197.             if(cfRGetVar('shufflePlaylist')){
  1198.                 $shuffle=cfRGetVar('playlist');
  1199.                 shuffle($shuffle);
  1200.                 cfRSetVar('playlistShuffled',$shuffle);
  1201.                 unset($shuffle);
  1202.             }
  1203.             $fileExtSrc=$passedFile;
  1204.         }
  1205.         // Play whole artist
  1206.         elseif (substr($passedFile,0,7)=='artist/'){
  1207.             @list($foo,$artist)=explode('/',$passedFile);
  1208.             $fileExtSrc='*tmp*playlist*.m3u';
  1209.             $tracks=sqlite_array_query("SELECT * FROM tracks WHERE Lower(Artist)='".strtolower(sqlite_escape_string($artist))."' LIMIT 200",$db,SQLITE_ASSOC);
  1210.             foreach ($tracks as $k=>$v) $tracks[$k]=mDBPlaylistItemFromTrack($v);
  1211.             cfRSetVar('playlistTmp',$tracks);
  1212.         }
  1213.         // Play whole album
  1214.         elseif (substr($passedFile,0,6)=='album/'){
  1215.             @list($foo,$artist,$album)=explode('/',$passedFile);
  1216.             $fileExtSrc='*tmp*playlist*.m3u';
  1217.             $tracks=sqlite_array_query("SELECT * FROM tracks WHERE Lower(Artist)='".strtolower(sqlite_escape_string($artist))."' AND Lower(Album)='".strtolower(sqlite_escape_string($album))."' LIMIT 200",$db,SQLITE_ASSOC);
  1218.             foreach ($tracks as $k=>$v) $tracks[$k]=mDBPlaylistItemFromTrack($v);
  1219.             cfRSetVar('playlistTmp',$tracks);
  1220.         }
  1221.         // If not playlist, artist or album, it's a track, identified by it's numerical id
  1222.         elseif(!is_numeric($passedFile)) {echo cfAsyncFooter();exit;}
  1223.         // Single mp3
  1224.         else {
  1225.             $fileExtSrc='*resSpecific*/'.$passedFile.'.mp3';
  1226.             $tags=@efGetAudioInfo(sqlite_single_query("SELECT Location FROM tracks WHERE id='".sqlite_escape_string($passedFile)."'",$db),false);
  1227.             cfRSetVar('singleMP3Label', rawurldecode($tags['label']));
  1228.         }
  1229.  
  1230.         // Play file by inserting flash player pointing on file
  1231.         $flashPlayer=vfInsertAudioPlayer($fileExtSrc,'normal',true,DEFAULT_INLINEPLAYER_WIDTH,25);
  1232.         echo cfAsyncXMLInnerHTMLbyId($flashPlayer,'playerDiv');
  1233.  
  1234.         // Update song info displayed on right-side of player
  1235.         echo cfAsyncXMLJSaction(inlineUpdatePlayerInfoScript($passedFile));
  1236.  
  1237.         @sqlite_close($db);
  1238.         die(cfAsyncFooter());
  1239.     }
  1240.     // Update cover & played track info (from information sent by flash player)
  1241.     elseif (cfRGetVar('inlinePlayer') && isset($_POST['data2']) && $action=='inlineUpdatePlayerInfo'){
  1242.         // Send async response header
  1243.         cfAsyncHeader();
  1244.         if(is_numeric($_POST['data2'])) echo cfAsyncXMLJSaction(inlineUpdatePlayerInfoScript($_POST['data2']));
  1245.         echo cfAsyncFooter();
  1246.         @sqlite_close($db);
  1247.         exit();
  1248.     }
  1249.  
  1250.     /**
  1251.      * Playlist related actions
  1252.      */
  1253.  
  1254.     // Add artist / playlist / track / random to playlist
  1255.     elseif (($action=='plAddArtist' || $action=='plAddPlaylist' || $action=='plAddTrack' || $action=='plAddAllShuffle') && isset($_POST['data2'])){
  1256.         // Send async response header
  1257.         cfAsyncHeader();
  1258.         playlistAddItem($action,$_POST['data2']);
  1259.         echo cfAsyncXMLJSaction('plRefresh('.efPlayListContentArray().')');
  1260.         echo efMultipleDownloadUpdateBt();
  1261.         echo cfAsyncXMLJSaction('mProcessing(false)');
  1262.         echo cfAsyncFooter();
  1263.         @sqlite_close($db);
  1264.         exit();
  1265.     }
  1266.     // Add album to playlist (data2=artist name, data4=album name)
  1267.     elseif ($action=='plAddAlbum' && isset($_POST['data2']) && isset($_POST['data4'])){
  1268.         // Send async response header
  1269.         cfAsyncHeader();
  1270.         playlistAddItem($action,$_POST['data2'],$_POST['data4']);
  1271.         echo cfAsyncXMLJSaction('plRefresh('.efPlayListContentArray().')');
  1272.         echo efMultipleDownloadUpdateBt();
  1273.         echo cfAsyncXMLJSaction('mProcessing(false)');
  1274.         echo cfAsyncFooter();
  1275.         @sqlite_close($db);
  1276.         exit();
  1277.     }
  1278.  
  1279.     // If no resource specific command is passed, process explorer common commands
  1280.     else efProcessPOST();
  1281.  
  1282.     // Common playlist actions
  1283.     if($action=='plAdd' || $action=='plSupp' || $action=='mSuppAll' || $action=='plUp' || $action=='plDown'){
  1284.         // Send async response header
  1285.         cfAsyncHeader();
  1286.         echo cfAsyncXMLJSaction('plRefresh('.efPlayListContentArray().')');
  1287.         // Update buttons
  1288.         echo efMultipleDownloadUpdateBt();
  1289.         // Update reloading icon
  1290.         echo cfAsyncXMLJSaction('mProcessing(false)');
  1291.         die(cfAsyncFooter());
  1292.     }
  1293.  
  1294.     // Async garbage
  1295.     if(cfIsAsync() && !cfIsMobile()){
  1296.         // Send async response header
  1297.         cfAsyncHeader();
  1298.         die(cfAsyncFooter());
  1299.     }
  1300. }
  1301.  
  1302. /**
  1303.  * @desc return JS code for async updating inline player cover and track info
  1304.  *
  1305.  * @param string $passedFile : track id or *playlist*.m3u
  1306.  * @return string : JS code
  1307.  */
  1308. function inlineUpdatePlayerInfoScript($passedFile){
  1309.         global $db;
  1310.         function cleanItem($utf8EncodedItem){
  1311.             $ci=cfXMLEncode(cfUTF8Encode(str_replace('"','\\"',cfStrTruncate(cfUTF8Decode($utf8EncodedItem,true,false,false),20)),false,false));
  1312.             if($ci) return $ci;
  1313.             return '-';
  1314.         }
  1315.  
  1316.         $jsAction='inlineSetSongPlayed(';
  1317.         // Playlist
  1318.         if(in_array(cfFileExtension($passedFile),$_ENV['supportedPlaylists']))
  1319.             $jsAction.='"'.dirname($_SERVER['PHP_SELF']).'/playlist.jpg","'.cfCaption('explorerAudioPlaylist').'","-","-")';
  1320.         // Single track
  1321.         else{
  1322.             $trackData=sqlite_array_query('SELECT Artist,Album,Name,Artwork FROM tracks WHERE id="'.sqlite_escape_string($passedFile).'"',$db,SQLITE_ASSOC);
  1323.             // Not found!
  1324.             if(!count($trackData)) return '';
  1325.             $artistName=$trackData[0]['Artist']; $albumName=$trackData[0]['Album']; $trackName=$trackData[0]['Name'];
  1326.             // Get audio track cover
  1327.             // If track has artwork info, use it
  1328.             if($trackData[0]['Artwork']) $art=$trackData[0]['Artwork'];
  1329.             // else, select artwork from other tracks from same artist/album
  1330.             else $art=sqlite_single_query('SELECT max(Artwork) FROM tracks WHERE Artist="'.sqlite_escape_string($artistName).'" AND Album="'.sqlite_escape_string($albumName).'"',$db);
  1331.             // If iTunes, add itc extension (not stored into db)
  1332.             //if(SOURCE_TYPE=='iTunes') $art.='.itc';
  1333.             if((SOURCE_TYPE!='iTunes' && $art) || strlen($art)>18) $jsAction.='"'.stripslashes(cfExtImage(cfRGetVar('path').'/'.$art,DEFAULT_INLINE_COVER_WIDTH,DEFAULT_INLINE_COVER_WIDTH)).'"';
  1334.             else  $jsAction.='"'.dirname($_SERVER['PHP_SELF']).'/cover.png"';
  1335.             // Display cover, artist, album and track name
  1336.             $jsAction.=',"'.cleanItem($artistName).'"'.',"'.cleanItem($albumName).'"'.',"'.cleanItem($trackName).'")';
  1337.         }
  1338.  
  1339.         return $jsAction;
  1340. }
  1341.  
  1342. /**
  1343.  * @desc return "unknown artist" if provided name is empty, else return name
  1344.  *
  1345.  * @param string $name
  1346.  * @return string
  1347.  */
  1348. function artistName($name){
  1349.     if($name=='') return cfCaption('explorerAudioUnknownArtist'); return $name;
  1350. }
  1351.  
  1352. /**
  1353.  * @desc return "unknown album" if provided name is empty, else return name
  1354.  *
  1355.  * @param string $name
  1356.  * @return string
  1357.  */
  1358. function albumName($name){
  1359.     if($name=='') return cfCaption('explorerAudioUnknownAlbum'); return $name;
  1360. }
  1361.  
  1362. /**
  1363.  * @desc Return current mixed groupBy/display type filter (artistFull, albumCompact...)
  1364.  *
  1365.  * @return string
  1366.  */
  1367. function mBDMixedFilter(){
  1368.     return str_replace('display',strtolower(cfRGetVar('groupBy')),cfRGetVar('displayType'));
  1369. }
  1370.  
  1371.  
  1372. /**
  1373.  * @desc Return displayType, artist data array, select list array, current offset
  1374.  * @return string : "display type, Array(Array('Artist1Name',nbAlbums1,nbTracks1,artSrc),...), Array('Artist1 - ArtistX','ArtistX+1 - Artist2X',...)
  1375.  *
  1376.  */
  1377. function groupByArtistData($filter=false){
  1378.     global $db;
  1379.  
  1380.     @sqlite_query('DROP VIEW tmpArtist',$db);
  1381.     if(SOURCE_TYPE=='iTunes') $op='max'; else $op='min'; // iTunes needs to get highest value as it contains empty elements, other need lowest as values are folderXXX or embedXXX, and embed is prefered over folder
  1382.     sqlite_query("CREATE VIEW tmpArtist AS SELECT max(Artist) as Artist, max(Album) AS Album, count(id) AS nbTracks, ".$op."(Artwork) AS art FROM tracks ".(($filter===false)?"":$filter." ")."GROUP BY lower(Album),lower(Artist);",$db);
  1383.     $list=sqlite_array_query("SELECT min(Artist) AS Artist, count(Album) AS nbAlbums, sum(nbTracks) AS nbTracks, ".$op."(art) AS art FROM tmpArtist GROUP BY lower(Artist) ORDER BY lower(Artist) ASC",$db,SQLITE_ASSOC);
  1384.  
  1385.     // Count results to detect result-less search request
  1386.     cfRSetVar('searchNbResults',cfRGetVar('searchNbResults')+count($list));
  1387.  
  1388.     /**
  1389.      * Generate content array
  1390.      */
  1391.     $nbItems=0;$pos=0;
  1392.     $array='"artist","'.strtolower(str_replace('display','',cfRGetVar('displayType'))).'","'.mBDMixedFilter().'", [';
  1393.     foreach ($list as $item){
  1394.         if(cfRGetVar('itemsPerPage')==0 || $filter!==false || ($pos>=cfRGetVar('displayOffset') && $pos<(cfRGetVar('displayOffset')+cfRGetVar('itemsPerPage')))){
  1395.             if($nbItems!=0) $array.=',';
  1396.             if(SOURCE_TYPE=='iTunes')
  1397.                 if(cfRGetVar('displayType')!=='displayCompact' && strlen($item['art'])>18)
  1398.                     $art=str_replace(cfRGetVar('Library Persistent ID'),'#',$item['art']);
  1399.                 else
  1400.                     $art='';
  1401.             else
  1402.                 $art=cfProtectFilenameURL($item['art']);
  1403.             $array.='["'.(esc(artistName($item['Artist']))).'",'.$item['nbAlbums'].','.$item['nbTracks'].',"'.$art.'"]';
  1404.             $nbItems++;
  1405.         }
  1406.         $pos++;
  1407.     }
  1408.     $array.='], ';
  1409.     reset($list);
  1410.     /**
  1411.      * Generate select array
  1412.      */
  1413.     if($filter!==false) $array.='[],';
  1414.     elseif(cfRGetVar('itemsPerPage')==0) $array.='[",'.esc2(artistName($list[0]['Artist'])).' <-----> '.esc2(artistName($list[count($list)-1]['Artist'])).'"],';
  1415.     else {
  1416.         $array.='[';
  1417.         for($pos=0;$pos<=floor((count($list)-1)/cfRGetVar('itemsPerPage'));$pos++){
  1418.             if($pos>0) $array.=',';
  1419.             if($pos*cfRGetVar('itemsPerPage')<=cfRGetVar('displayOffset') && ($pos+1)*cfRGetVar('itemsPerPage')>cfRGetVar('displayOffset')) $array.='"S'; else $array.='"N';
  1420.             $array.=esc2(artistName($list[$pos*cfRGetVar('itemsPerPage')]['Artist'])).' <-----> '.esc2(artistName($list[min(count($list)-1,(($pos+1)*cfRGetVar('itemsPerPage')-1))]['Artist'])).'"';
  1421.         }
  1422.         $array.='], ';
  1423.     }
  1424.  
  1425.     /**
  1426.      * Current offset
  1427.      */
  1428.     return $array.(int)cfRGetVar('displayOffset');
  1429. }
  1430.  
  1431. /**
  1432.  * @desc Return displayType, album data array, select list array, current offset
  1433.  * @return string : "display type, Array(Array('Album1Name','Artist1Name',nbTracks1,totalTime1),...),...),...)
  1434.  */
  1435. function groupByAlbumData($filter=false){
  1436.  
  1437.     global $db;
  1438.     // iTunes needs to get highest value as it contains empty elements, other need lowest as values are folderXXX or embedXXX, and embed is prefered over folder
  1439.     if(SOURCE_TYPE=='iTunes') $op='max'; else $op='min';
  1440.     $list=sqlite_array_query("SELECT min(Album) AS Album, min(Artist) AS Artist, ".$op."(Artwork) AS art, count(*) AS nbTracks, sum(TotalTime) AS totalTime FROM tracks ".(($filter===false)?'':$filter.' ')."GROUP BY lower(Album), lower(Artist) ORDER BY lower(Artist) ASC",$db,SQLITE_ASSOC);
  1441.  
  1442.     // Count results to detect result-less search request
  1443.     cfRSetVar('searchNbResults',cfRGetVar('searchNbResults')+count($list));
  1444.  
  1445.     /**
  1446.      * Generate content array
  1447.      */
  1448.     $nbItems=0;$pos=0;
  1449.     $array='"album","'.strtolower(str_replace('display','',cfRGetVar('displayType'))).'","'.mBDMixedFilter().'", [';
  1450.     foreach ($list as $item){
  1451.         if(cfRGetVar('itemsPerPage')==0 || $filter!==false || ($pos>=cfRGetVar('displayOffset') && $pos<(cfRGetVar('displayOffset')+cfRGetVar('itemsPerPage')))){
  1452.             if($nbItems!=0) $array.=',';
  1453.             if(SOURCE_TYPE=='iTunes')
  1454.                 if(cfRGetVar('displayType')!=='displayCompact' && strlen($item['art'])>18) $art=str_replace(cfRGetVar('Library Persistent ID'),'#',$item['art'])/*.'.itc'*/; else $art='';
  1455.             else $art=cfProtectFilenameURL($item['art']);
  1456.  
  1457.             $array.='["'.(esc(albumName($item['Album']))).'","'.(esc(artistName($item['Artist']))).'",'.$item['nbTracks'].','.$item['totalTime'].',"'.$art.'"]';
  1458.             $nbItems++;
  1459.         }
  1460.         $pos++;
  1461.     }
  1462.     $array.='], ';
  1463.     reset($list);
  1464.  
  1465.     /**
  1466.      * Generate select array
  1467.      */
  1468.     if($filter!==false) $array.='[],';
  1469.     elseif(cfRGetVar('itemsPerPage')==0) $array.='[",'.esc2(albumName($list[0]['Artist'])).' <-----> '.esc2(albumName($list[count($list)-1]['Artist'])).'"],';
  1470.     else {
  1471.         $array.='[';
  1472.         for($pos=0;$pos<=floor((count($list)-1)/cfRGetVar('itemsPerPage'));$pos++){
  1473.             if($pos>0) $array.=',';
  1474.             if($pos*cfRGetVar('itemsPerPage')<=cfRGetVar('displayOffset') && ($pos+1)*cfRGetVar('itemsPerPage')>cfRGetVar('displayOffset')) $array.='"S'; else $array.='"N';
  1475.             $array.=esc2(albumName($list[$pos*cfRGetVar('itemsPerPage')]['Artist'])).' <-----> '.esc2(albumName($list[min(count($list)-1,(($pos+1)*cfRGetVar('itemsPerPage')-1))]['Artist'])).'"';
  1476.         }
  1477.         $array.='], ';
  1478.     }
  1479.  
  1480.     /**
  1481.      * Current offset
  1482.      */
  1483.     return $array.(int)cfRGetVar('displayOffset');
  1484. }
  1485.  
  1486. /**
  1487.  * @desc Return displayType('compact'), playlist data array, select list array, current offset
  1488.  * @return string : "display type, Array(Array('PlaylistID','PlaylistName',nbTracks1),...), Array(...), current offset
  1489.  */
  1490. function groupByPlaylistData($filter=false){
  1491.     global $db;
  1492.     $list=sqlite_array_query("SELECT playlists.playlistId AS PlaylistID, playlists.Name AS PlaylistName, count(*) AS nbTracks FROM playlists INNER JOIN playlistsTracks ON playlists.PlaylistID=playlistsTracks.PlaylistID ".(($filter===false)?'':$filter.' ')."GROUP BY playlists.playlistId",$db);
  1493.  
  1494.     // Count results to detect result-less search request
  1495.     cfRSetVar('searchNbResults',cfRGetVar('searchNbResults')+count($list));
  1496.  
  1497.     /**
  1498.      * Generate content array
  1499.      */
  1500.     $nbItems=0;$pos=0;
  1501.     $array='"playlist","'.strtolower(str_replace('display','',cfRGetVar('displayType'))).'","'.mBDMixedFilter().'", [';
  1502.     foreach ($list as $item){
  1503.         if(cfRGetVar('itemsPerPage')==0 || $filter!==false || ($pos>=cfRGetVar('displayOffset') && $pos<(cfRGetVar('displayOffset')+cfRGetVar('itemsPerPage')))){
  1504.             if($nbItems!=0) $array.=',';
  1505.             $array.='["'.$item['PlaylistID'].'","'.esc($item['PlaylistName']).'",'.$item['nbTracks'].']';
  1506.             $nbItems++;
  1507.         }
  1508.         $pos++;
  1509.     }
  1510.     $array.='], ';
  1511.     reset($list);
  1512.     /**
  1513.      * Generate select array
  1514.      */
  1515.     if($filter!==false) $array.='Array(),';
  1516.     elseif(cfRGetVar('itemsPerPage')==0) $array.='Array(",'.esc2($list[0]['PlaylistName']).' <-----> '.esc2($list[count($list)-1]['PlaylistName']).'"),';
  1517.     else {
  1518.         $array.='[';
  1519.         for($pos=0;$pos<=floor((count($list)-1)/cfRGetVar('itemsPerPage'));$pos++){
  1520.             if($pos>0) $array.=',';
  1521.             if($pos*cfRGetVar('itemsPerPage')<=cfRGetVar('displayOffset') && ($pos+1)*cfRGetVar('itemsPerPage')>cfRGetVar('displayOffset')) $array.='"S'; else $array.='"N';
  1522.             $array.=esc2($list[$pos*cfRGetVar('itemsPerPage')]['PlaylistName']).' <-----> '.esc2($list[min(count($list)-1,(($pos+1)*cfRGetVar('itemsPerPage')-1))]['PlaylistName']).'"';
  1523.         }
  1524.         $array.='], ';
  1525.     }
  1526.     /**
  1527.      * Current offset
  1528.      */
  1529.     return $array.(int)cfRGetVar('displayOffset');
  1530. }
  1531.  
  1532. /**
  1533.  * @desc Return tracks array() matching filter
  1534.  * @return string : "Array(Array('trackId','trackName','albumName','artistName','totalTime1),Array(),...)
  1535.  */
  1536. function groupByTrackData($filter=false){
  1537.     global $db;
  1538.  
  1539.     // Get matching tracks
  1540.     $list=sqlite_array_query("SELECT id, name, totalTime, Album, Artist FROM tracks ".(($filter===false)?'':$filter.' ')."GROUP BY lower(Album), lower(Artist), name ORDER BY lower(Album) ASC".((cfRGetVar('itemsPerPage'))?" LIMIT ".cfRGetVar('itemsPerPage'):''),$db,SQLITE_ASSOC);
  1541.     $filter='';
  1542.  
  1543.     // Count results to detect result-less search request
  1544.     cfRSetVar('searchNbResults',cfRGetVar('searchNbResults')+count($list));
  1545.  
  1546.     /**
  1547.      * Generate content array
  1548.      */
  1549.     $tracks='';
  1550.     foreach ($list as $item){
  1551.         $tracks.=',["'.$item['id'].'","'.(esc(artistName($item['name']))).'","'.(esc(albumName($item['Album']))).'","'.(esc(artistName($item['Artist']))).'",'.$item['totalTime'].']';
  1552.     }
  1553.     return '"tracks","detail","tracksDetail", ['.substr($tracks,1).'],[],0';
  1554. }
  1555.  
  1556.  
  1557. function mDBCaseSort($a,$b){
  1558.     $l=strlen($a);
  1559.     for($i=0;$i<$l;$i++){
  1560.         if($a[$i]!=$b[$i]){
  1561.             if(strtolower($a[$i])==$a[$i]) return -1; else return 1;
  1562.         }
  1563.     }
  1564.     return 0;
  1565. }
  1566.  
  1567. /**
  1568.  * @desc async return Javascript code for displaying artist detail (list of tracks grouped by album)
  1569.  *
  1570.  * @param string $artistName : utf-8 encoded artist name
  1571.  * @param string $destId : id of node into which HTML code should be inserted
  1572.  *
  1573.  * @return output : "Artist", displayType, destId, Array(
  1574.  *                         Array(Album1 name, Album1 artwork, Album1 year, Album1 total time, Album1 nb tracks,
  1575.  *                             Array(
  1576.  *                                 Array(Track1 ID, Track1 name, Track1 total time),
  1577.  *                                 Array(Track2 ID, Track2 name, Track2 total time),
  1578.  *                                 ...
  1579.  *                         ...),
  1580.  *                     )
  1581.  */
  1582. function showDetailArtist($artistName, $destId){
  1583.     global $db;
  1584.     // Empty artist caption : replace by empty string
  1585.     if($artistName==cfCaption('explorerAudioUnknownArtist')) $artistName='';
  1586.  
  1587.     $list=sqlite_array_query("SELECT Album, id, PersistentId, Name, Artwork, Year, TotalTime FROM tracks WHERE lower(Artist)='".strtolower(sqlite_escape_string($artistName))."' ORDER BY lower(Album) ASC, TrackNumber ASC",$db,SQLITE_ASSOC);
  1588.     $nbTracks=0;
  1589.     $array=''; $tracks='';
  1590.     $album=''; $artwork=''; $year=0; $totalTime=0; $nbTracks=0;
  1591.  
  1592.     // Keep "upercasemost" version of albums names
  1593.     $ucAlbums=array();
  1594.     foreach ($list as $track) if(!isset($ucAlbums[strtolower($track['Album'])]) || mDBCaseSort($ucAlbums[strtolower($track['Album'])],$track['Album'])<0)
  1595.         $ucAlbums[strtolower($track['Album'])]=$track['Album'];
  1596.     foreach ($list as $id=>$track) $list[$id]['Album']=$ucAlbums[strtolower($track['Album'])];
  1597.  
  1598.     foreach ($list as $track){
  1599.         // Write previous album data
  1600.         if(albumName($track['Album'])!=$album && $album!=''){
  1601.             $array.=',Array("'.esc($album).'","'.$artwork.'","'.$year.'",'.$totalTime.','.$nbTracks.',Array('.substr($tracks,1).'))'."\n";
  1602.             $artwork=''; $year=0; $totalTime=0; $nbTracks=0;$tracks='';
  1603.         }
  1604.         $album=$track['Album'];
  1605.         $tracks.=',["'.$track['id'].'","'.esc($track['Name']).'",'.$track['TotalTime'].']';
  1606.         $nbTracks++;
  1607.         $totalTime+=$track['TotalTime'];
  1608.         if(isset($track['year']) && $track['year']) $year=$track['year'];
  1609.  
  1610.         if(SOURCE_TYPE=='iTunes')
  1611.             {if(!$artwork && strlen($track['Artwork'])>18) $artwork=str_replace(cfRGetVar('Library Persistent ID'),'#',$track['Artwork'])/*.'.itc'*/;}
  1612.         else
  1613.             {
  1614.                 if(!$artwork || (substr($artwork,0,6)=='folder' && substr($track['Artwork'],0,5)=='embed')) $artwork=cfProtectFilenameURL($track['Artwork']);
  1615.             }
  1616.  
  1617.     }
  1618.     $array.=',["'.esc($album).'","'.$artwork.'","'.$year.'",'.$totalTime.','.$nbTracks.',['.substr($tracks,1).']]';
  1619.     $array=str_replace(chr(0),'',$array);
  1620.  
  1621.     echo cfAsyncXMLJSaction('insertDetail("Artist", "'.cfRGetVar('displayType').'", "'.$destId.'",['.substr($array,1).'])');
  1622. }
  1623.  
  1624. /**
  1625.  * @desc async return Javascript code for displaying single album detail
  1626.  *
  1627.  * @param string $artistName : utf-8 encoded artist name
  1628.  * @param string $destId : id of node into which HTML code should be inserted
  1629.  *
  1630.  * @return output : "Album", displayType, destId,
  1631.  *                             Array(
  1632.  *                                 Array(Track1 ID, Track1 name, Track1 total time),
  1633.  *                                 Array(Track2 ID, Track2 name, Track2 total time),
  1634.  *                                 ...
  1635.  *                         ...),
  1636.  *                     )
  1637.  */
  1638. function showDetailAlbum($albumName, $artistName, $destId){
  1639.     global $db;
  1640.  
  1641.     // Empty album caption : replace by empty string
  1642.     if($albumName==cfCaption('explorerAudioUnknownAlbum')) $albumName='';
  1643.     $list=sqlite_array_query("SELECT Album, id, Name, TrackNumber, TotalTime FROM tracks WHERE lower(Artist)='".strtolower(sqlite_escape_string($artistName))."' AND lower(Album)='".strtolower(sqlite_escape_string($albumName))."' ORDER BY TrackNumber ASC",$db);
  1644.  
  1645.     $tracks='';
  1646.     foreach ($list as $track){
  1647.         $tracks.=',["'.$track['id'].'","'.esc($track['Name']).'",'.$track['TotalTime'].']';
  1648.     }
  1649.     $tracks=str_replace(chr(0),'',$tracks);
  1650.     echo cfAsyncXMLJSaction('insertDetail("Album", "'.cfRGetVar('displayType').'", "'.$destId.'",['.substr($tracks,1).'])');
  1651. }
  1652.  
  1653. /**
  1654.  * @desc async return Javascript code for displaying playlist detail (list of tracks)
  1655.  *
  1656.  * @param string $playlistId : playlist id
  1657.  * @param string $destId : id of node into which HTML code should be inserted
  1658.  *
  1659.  * @return output : "Playlist", displayType, destId, Array(
  1660.  *                         Array(
  1661.  *                             Array(Track1 ID, Track1 name, Track1 total time, Track1 artist, Track1 album),
  1662.  *                             Array(Track2 ID, Track2 name, Track2 total time, Track2 artist, Track2 album),
  1663.  *                             ...
  1664.  *                         ),
  1665.  *                     )
  1666.  */
  1667. function showDetailPlaylist($playlistId, $destId){
  1668.     global $db;
  1669.     $list=sqlite_array_query("SELECT t.id AS id, t.Artist AS Artist, t.Album AS Album, t.Name AS Name, t.TotalTime AS TotalTime FROM playlistsTracks pt INNER JOIN tracks t ON t.id=pt.trackId WHERE pt.PlaylistID='".$playlistId."'",$db);
  1670.  
  1671.     $tracks='';
  1672.     foreach ($list as $track){
  1673.         $tracks.=',["'.$track['id'].'","'.esc($track['Name']).'",'.$track['TotalTime'].',"'.esc(artistName($track['Artist'])).'","'.esc(artistName($track['Album'])).'"]';
  1674.     }
  1675.     $tracks=str_replace(chr(0),'',$tracks);
  1676.     echo cfAsyncXMLJSaction('insertDetail("Playlist", "'.cfRGetVar('displayType').'", "'.$destId.'",['.substr($tracks,1).'])');
  1677. }
  1678.  
  1679. /**
  1680.  * @desc return playlist track label from data present in tracks table
  1681.  * @param $trackArray : array(id, Artist, Album, Name)
  1682.  * @return string : label
  1683.  *
  1684.  */
  1685. function playlistLabelFromTrackInfo($trackArray){
  1686.     $label=artistName($trackArray['Artist']).' - '.albumName($trackArray['Album']);
  1687.     if(isset($trackArray['TrackNumber']) && $trackArray['TrackNumber']) $label.=' - '.$trackArray['TrackNumber'];
  1688.     return $label.' - '.$trackArray['Name'];
  1689. }
  1690.  
  1691. /**
  1692.  * @desc add a track, album, artist or playlist to playlist
  1693.  *
  1694.  * @param string $itemType : 'plAddTrack' or 'plAddAlbum' or 'plAddArtist' or 'plAddPlaylist'.
  1695.  * @param mixed $itemId : track id if 'plAddTrack', playlist id if 'plAddPlaylist', else artist name
  1696.  * @param mixed $albumName : album name (only for 'plAddAlbum')
  1697.  */
  1698. function playlistAddItem($itemType, $itemId, $albumName=false){
  1699.     global $db;
  1700.     // Get actual playlist content
  1701.     $arr=cfRGetVar('playlist');
  1702.  
  1703.     // Clear unknown album and unknown artist values
  1704.     if($itemId==cfCaption('explorerAudioUnknownArtist')) $itemId=''; //else $itemId=strtolower((cfUTF8Encode(strtolower(cfUTF8Decode($itemId)))));
  1705.     if($albumName==cfCaption('explorerAudioUnknownAlbum')) $albumName=''; //else $albumName=strtolower((cfUTF8Encode(strtolower(cfUTF8Decode($albumName)))));
  1706.     $itemId=strtolower($itemId);
  1707.     $albumName=strtolower($albumName);
  1708.  
  1709.     // Set file properties
  1710.     switch ($itemType) {
  1711.         case 'plAddArtist':
  1712.             $list=sqlite_array_query("SELECT id, Artist, Album, TrackNumber, Name FROM tracks WHERE lower(Artist)='".sqlite_escape_string($itemId)."' ORDER BY lower(Artist) ASC, lower(Album) ASC, TrackNumber ASC",$db);
  1713.             foreach ($list as $track)
  1714.                 $arr[]=array('completeFileName'=>'*resSpecific*/'.$track['id'].'.mp3', 'name'=>'*resSpecific*/'.$track['id'].'.mp3', 'label'=>cfUTF8Decode(playlistLabelFromTrackInfo($track)), 'externalRef'=>$track['id']);
  1715.             break;
  1716.  
  1717.         case 'plAddAlbum':
  1718.             $list=sqlite_array_query("SELECT id, Artist, Album, TrackNumber, Name FROM tracks WHERE lower(Artist)='".sqlite_escape_string($itemId)."' AND lower(Album)='".sqlite_escape_string($albumName)."' ORDER BY lower(Artist) ASC, lower(Album) ASC, TrackNumber ASC",$db);
  1719.             foreach ($list as $track)
  1720.                 $arr[]=array('completeFileName'=>'*resSpecific*/'.$track['id'].'.mp3', 'name'=>'*resSpecific*/'.$track['id'].'.mp3', 'label'=>cfUTF8Decode(playlistLabelFromTrackInfo($track)), 'externalRef'=>$track['id']);
  1721.             break;
  1722.  
  1723.         case 'plAddPlaylist':
  1724.             $list=sqlite_array_query("SELECT t.id AS id, t.Artist AS Artist, t.Album AS Album, t.TrackNumber AS TrackNumber, t.Name AS NAME FROM tracks t INNER JOIN playlistsTracks pt ON t.id=pt.TrackID WHERE pt.PlaylistID='".sqlite_escape_string($itemId)."'",$db);
  1725.             foreach ($list as $track)
  1726.                 $arr[]=array('completeFileName'=>'*resSpecific*/'.$track['id'].'.mp3', 'name'=>'*resSpecific*/'.$track['id'].'.mp3', 'label'=>cfUTF8Decode(playlistLabelFromTrackInfo($track)), 'externalRef'=>$track['id']);
  1727.             break;
  1728.  
  1729.         case 'plAddTrack':
  1730.             $list=sqlite_array_query("SELECT id, Artist, Album, TrackNumber, Name FROM tracks WHERE id='".sqlite_escape_string($itemId)."'",$db);
  1731.             if(count($list)==1){
  1732.                 $arr[]=array('completeFileName'=>'*resSpecific*/'.$itemId.'.mp3', 'name'=>'*resSpecific*/'.$itemId.'.mp3', 'label'=>cfUTF8Decode(playlistLabelFromTrackInfo($list[0])), 'externalRef'=>$list[0]['id']);
  1733.             }
  1734.             break;
  1735.         case 'plAddAllShuffle':
  1736.             $arr=array();// Clear previous playlist
  1737.             $list=sqlite_array_query("SELECT id, Artist, Album, TrackNumber, Name FROM tracks ORDER BY Random() LIMIT ".max(1,cfRGetVar('plAddAllShuffleSize')),$db);
  1738.             foreach ($list as $track)
  1739.                 $arr[]=array('completeFileName'=>'*resSpecific*/'.$track['id'].'.mp3', 'name'=>'*resSpecific*/'.$track['id'].'.mp3', 'label'=>cfUTF8Decode(playlistLabelFromTrackInfo($track)), 'externalRef'=>$track['id']);
  1740.             break;
  1741.  
  1742.             break;
  1743.         default:
  1744.             break;
  1745.     }
  1746.     // & issue (generated by cleanHTML option of cfUTF8Decode)
  1747.     foreach ($arr as $k=>$v) $arr[$k]['label']=str_replace('&','&',$arr[$k]['label']);
  1748.  
  1749.     // Commit changes to playlist
  1750.     cfRUnsetVar('playlist');
  1751.     cfRSetVar('playlist',$arr);
  1752.     cfRSetVar('noFileRefresh',true);
  1753.  
  1754.     // Create multiple download list from playlist
  1755.     efConvertPlayListToMDList();
  1756. }
  1757.  
  1758. /**
  1759.  * @desc display filter area HTML code
  1760.  * @return void
  1761.  *
  1762.  */
  1763. function displayFilter(){
  1764.     global $itemsPerPageList;
  1765.  
  1766.     // Database update button
  1767.     $output=outDivFrame('frame1','id="filterDiv"','width:16em');
  1768.     if(cfRGetVar('databaseUpdateMethod')=='manual') {
  1769.         $r=outButton(cfCaption('genDataUpdate'),'javascript:updateDatabase()',outIcon('change'));
  1770.     }
  1771.     else $r='';
  1772.     $output.='<div class="frame1Header">'.cfCaption('genFilter')."</div>\n";
  1773.     $output.=$r;
  1774.  
  1775.     /**
  1776.      * Search
  1777.      */
  1778.     $output.=outDivFrame('frame2');
  1779.     $output.='<div class="frame2Header"><input id="groupBySearch" type="radio" name="groupBy" value="Search"'.((cfRGetVar('groupBy')=='Search')?' checked="checked"':'').'>  '.cfCaption('genSearch').'</div>';
  1780.     $output.='<input class="textInput" style="width:60%;margin-right:1em" id="searchInput" onkeyup="validateSearch()" autocorrect="off" autocapitalize="off" value="'.((cfRGetVar('groupBy')=='Search')?str_replace('%','',cfRGetVar('searchValue')):'').'"/>';
  1781.     $output.=outButton('','javascript:search(dgi(\'searchInput\').value)',outIcon('search'));
  1782.     $output.="</div>\n\n";
  1783.  
  1784.     /**
  1785.      * Group by
  1786.      */
  1787.  
  1788.     $output.=outDivFrame('frame2');
  1789.     $output.='<div class="frame2Header">'.cfCaption('genGroupBy').'</div>';
  1790.  
  1791.     // Group by artist
  1792.     $output.='<div id="groupByArtistDiv" eH=1 style="width:95%;cursor:pointer;" onclick="groupBy(\'Artist\');">';
  1793.     $output.='<input id="groupByArtist" type="radio" name="groupBy" value="Artist"'.((cfRGetVar('groupBy')=='Artist')?' checked="checked"':'').'>  '.cfCaption('explorerAudioArtist').'</div>';
  1794.  
  1795.     // Group by album
  1796.     $output.='<div id="groupByAlbumIdDiv" eH=1 style="width:95%;cursor:pointer;" onclick="groupBy(\'Album\');">';
  1797.     $output.='<input id="groupByAlbum" type="radio" name="groupBy" value="Album"'.((cfRGetVar('groupBy')=='Album')?' checked="checked"':'').'>  '.cfCaption('explorerAudioAlbum').'</div>';
  1798.  
  1799.     //$output.='<div id="groupByYearIdDiv" class="eTC" eH=1 style="width:95%;cursor:pointer;" onclick="groupBy(\'Year\');">';
  1800.     //$output.='<input id="groupByYear" type="radio" name="groupBy" value="Year"'.((cfRGetVar('groupBy')=='Year')?' checked="checked"':'').'>  '.cfCaption('explorerAudioYear').'</div>';
  1801.  
  1802.     // Group by playlist
  1803.     $output.='<div id="groupByPlaylistDiv" eH=1 style="width:95%;cursor:pointer;" onclick="groupBy(\'Playlist\');">';
  1804.     $output.='<input id="groupByPlaylist" type="radio" name="groupBy" value="Playlist"'.((cfRGetVar('groupBy')=='Playlist')?' checked="checked"':'').'>  '.cfCaption('explorerAudioPlaylist').'</div>';
  1805.     $output.="</div>\n\n";
  1806.  
  1807.     /**
  1808.      * Display options
  1809.      */
  1810.     $output.=outDivFrame('frame2');
  1811.     $output.='<div class="frame2Header">'.cfCaption('genDisplay').'</div>';
  1812.  
  1813.     // Items per page
  1814.     $output.='<div>'.cfCaption('explorerItemsPerPage');
  1815.     $output.='<select id="itemsPerPage" size="1" class="textInput" onChange="changeItemsPerPage(this.value)" style="margin-left:1em">';
  1816.     foreach ($itemsPerPageList as $value){
  1817.         $output.='<option value="'.$value.'" '.(($value==cfRGetVar('itemsPerPage'))?'selected="selected"':'').'>'.(($value==0)?cfCaption('genAll').' ':$value).'</option>';
  1818.     }
  1819.     $output.='</select></div><br/>';
  1820.  
  1821.     // Compact / Full / Cover display
  1822.     $output.='<div>'.cfCaption('genDisplay').'</div><center>';
  1823.  
  1824.     $displayTypes=array('displayCompact'=>'displayFile1.gif','displayFull'=>'displayFull.gif','displayCovers'=>'displayIcons.gif');
  1825.     foreach ($displayTypes as $dt=>$icon) $output.=outBlockItem('displayType',outImage('/gfx/'.$icon),false,'padding:10px',array('onclick'=>'changeDisplayType(\''.$dt.'\')'),(cfRGetVar('displayType')==$dt)?'sel':'unsel');
  1826.     $output.='</center>';
  1827.     $output.="</div>\n\n";
  1828.  
  1829.     $output.="</div>\n\n";
  1830.  
  1831.     $output.="</div>\n\n";
  1832.     return $output;
  1833. }
  1834.  
  1835. /**
  1836.  * @desc refresh selection area content
  1837.  *
  1838.  */
  1839. function asyncRefreshDisplay(){
  1840.     switch (cfRGetVar('groupBy')) {
  1841.         // Search in Artists, Albums, Tracks
  1842.         case 'Search':
  1843.             cfRSetVar('searchNbResults',0);// Reset search counter
  1844.             // Exception for unknown artist or album : replace by empty string
  1845.             if(cfUTF8Decode(@$_POST['data2'])==cfCaption('explorerAudioUnknownArtist') || cfUTF8Decode(@$_POST['data2'])==cfCaption('explorerAudioUnknownAlbum')) $_POST['data2']='';
  1846.             // Clear results area
  1847.             echo cfAsyncXMLJSaction('if(dgi("selectionDiv")) dgi("selectionDiv").innerHTML="";');
  1848.             echo cfAsyncXMLJSaction('if(dgi("mainDiv")) dgi("mainDiv").innerHTML="";');
  1849.  
  1850.             // Search in artists
  1851.             $filter="WHERE lower(Artist) LIKE '".strtolower(sqlite_escape_string(((cfRGetVar('searchValue')==cfCaption('explorerAudioUnknownArtist'))?'':cfRGetVar('searchValue'))))."'";
  1852.             echo cfAsyncXMLJSaction('displayList('.groupByArtistData($filter).',0)');
  1853.  
  1854.             // Search in albums
  1855.             $filter="WHERE (lower(Artist) NOT LIKE '".strtolower(sqlite_escape_string(cfRGetVar('searchValue')))."') AND (lower(Album) LIKE '".sqlite_escape_string(strtolower(((cfRGetVar('searchValue')==cfCaption('explorerAudioUnknownAlbum'))?'':cfRGetVar('searchValue'))))."')";
  1856.             echo cfAsyncXMLJSaction('displayList('.groupByAlbumData($filter).',0)');
  1857.  
  1858.             // Search in tracks
  1859.             $filter="WHERE (lower(Artist) NOT LIKE '".sqlite_escape_string(strtolower(cfRGetVar('searchValue')))."') AND (lower(Album) NOT LIKE '".sqlite_escape_string(strtolower(cfRGetVar('searchValue')))."') AND (lower(Name) LIKE '".sqlite_escape_string(strtolower(cfRGetVar('searchValue')))."')";
  1860.             echo cfAsyncXMLJSaction('displayList('.groupByTrackData($filter).',0)');
  1861.  
  1862.             if(cfRGetVar('searchNbResults')==0) echo cfAsyncXMLJSaction('displayNoResults()');
  1863.  
  1864.             break;
  1865.         case 'Artist':
  1866.             echo cfAsyncXMLJSaction('displayList('.groupByArtistData().',true)');
  1867.             break;
  1868.         case 'Album':
  1869.             echo cfAsyncXMLJSaction('displayList('.groupByAlbumData().',true)');
  1870.             break;
  1871.         case 'Year':
  1872.             break;
  1873.         case 'Playlist':
  1874.             echo cfAsyncXMLJSaction('displayList('.groupByPlaylistData().',true)');
  1875.             break;
  1876.  
  1877.         default:
  1878.             break;
  1879.     }
  1880.  
  1881. //echo microtime(true)-$a;
  1882. //dbDisplayTable('SELECT Album from tracks GROUP BY Album;');
  1883. //@sqlite_query('CREATE VIEW artAlb AS SELECT Artist, Album, count(id) AS nbTracks, (min(Artwork || PersistentID)) AS art FROM tracks GROUP BY Artist, Album;',$db);
  1884. //dbDisplayTable('SELECT * FROM artAlb');
  1885. //dbDisplayTable('SELECT Artist, sum(nbTracks) AS nbTracks2, count(Album) AS nbAlbums FROM artAlb WHERE lower(Artist) < \'benabar\' GROUP BY Artist ORDER BY lower(Artist) ASC;');
  1886. //sqlite_query('DROP VIEW artAlb',$db);
  1887. //dbDisplayTable('library');
  1888. //dbDisplayTable('playlistsTracks');
  1889. }
  1890.  
  1891. /**
  1892.  * @desc display Inline player HTML code
  1893.  * @return void
  1894.  *
  1895.  */
  1896. function displayInlinePlayer(){
  1897.     outShadowBefore(DEFAULT_INLINEPLAYER_WIDTH.'px');
  1898.     echo outDivFrame('frame1','id="inlinePlayer"','padding-bottom:0');
  1899.     echo '<div class="frame1Header">'.outThemeImage(dirname($_SERVER['PHP_SELF']).'/player.png',false,false,'vertical-align:middle; margin-right:1em').cfCaption('explorerAudioPlayer').outButton('','#',false,false,false,'style="visibility:hidden"').'</div>';
  1900.     //echo outFrameHeaderTable('frame1Header',outThemeImage(dirname($_SERVER['PHP_SELF']).'/player.png',false,false,'vertical-align:middle; margin-right:1em').cfCaption('explorerAudioPlayer'),outButton(false,'javascript:togglePlayerDisplay()',outIcon('minus'),false,'togglePlayerBt'));
  1901.  
  1902.     echo outDivFrame('frame2','id="inlinePlayerInnerFrame"');
  1903.     //echo '<div class="frame2Header">'.cfCaption('musicInlinePlaying').'</div>';
  1904.     require(INCLUDE_DIR.'viewFunctions.php');
  1905.     echo outDivFrame('dropTarget', 'id="inlinePlayerDiv" name="playListFrame"','height:'.DEFAULT_INLINEPLAYER_HEIGHT.';');
  1906.     echo outTableTransparent('frame2').'<tr><td width="1" style="vertical-align:top"><div style="position:relative;margin:-4px 0.5em 0.5em -4px ;width:88px;height:88px">';
  1907.     echo outImage('/gfx/icons/blankIcon.gif',false,'id="inlinePlayerImg"','position:relative;left:4px;top:4px;background:#4d4d4d');
  1908.     echo outImage(dirname($_SERVER['PHP_SELF']).'/../music2/artistBg.png',false,'','position:absolute;top:0;left:0');
  1909.     echo '</div>';
  1910.     echo '</td><td>';
  1911.     echo '<b>'.cfCaption('explorerAudioArtist').'</b><div id="inlineArtist">-</div>';
  1912.     echo '<b>'.cfCaption('explorerAudioAlbum').'</b><div id="inlineAlbum">-</div>';
  1913.     echo '<b>'.cfCaption('explorerAudioTitle').'</b><div id="inlineTitle">-</div>';
  1914.     echo '</td></tr></table>';
  1915.     // Non-Wii player
  1916.     if(!cfIsWII())echo '<center id="playerDiv">'.vfInsertAudioPlayer('','normal',false,DEFAULT_INLINEPLAYER_WIDTH,25).'</center>';
  1917.  
  1918.     ?>
  1919. <script type="text/javascript">
  1920. function flashCommand(cmd,args) {
  1921.     <?php if(cfRGetVar('inlinePlayer')) echo 'if(cmd=="playSong") inlineUpdatePlayerInfo(args);';?>
  1922. }
  1923. </script>
  1924.     <?php
  1925.     echo '</div></div>';
  1926.     outFrameShadowAfter();
  1927. }
  1928.  
  1929. /**
  1930.  * @desc display Javascript functions
  1931.  * @return void
  1932.  *
  1933.  */
  1934. function displayJavascript(){
  1935. ?>
  1936. <script type="text/javascript">
  1937. var seqId=0;
  1938. var supportsPlaylist=<?php echo (int)(cfBGetVar('playlistFormat'));?>;
  1939. var margins=<?php if(cfGetBrowser()=='ie') echo 20; else echo 25;?>;
  1940. var lPID="<?php echo cfRGetVar('Library Persistent ID');?>";
  1941. var coverTemplate="<?php echo cfExtImage('*resourceBasePath*/IMAGEPATH',FULLVIEW_COVER_WIDTH,FULLVIEW_COVER_WIDTH,true,0,array('utf8Decode'=>true),true);?>";
  1942. var hmsCaption="<?php echo cfCaptionJS('javascriptHourMinSecFormat');?>";
  1943. var msCaption="<?php echo cfCaptionJS('javascriptMinSecFormat');?>";
  1944. var sourceType="<?php echo SOURCE_TYPE;?>";
  1945. var maxDragItems=<?php echo cfHiGetVar('maxDragItems',80);?>;
  1946. var searchValue;
  1947. var coverDefaultSrc='<?php echo dirname($_SERVER['PHP_SELF']);?>/../music2/cover.png';
  1948. var dAWW=<?php echo DEFAULT_AUDIO_WINDOW_WIDTH;?>, dAWH=<?php echo DEFAULT_AUDIO_WINDOW_HEIGHT;?>,fCW=<?php echo FULLVIEW_COVER_WIDTH;?>;
  1949. </script>
  1950. <?php
  1951.     echo cfScriptLink('musicDB.js');
  1952. }